From 42fb41d149c1a43bfd00fccd69011de3da2a9eec Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sat, 28 Dec 2024 20:21:48 +0300 Subject: [PATCH 01/11] Add is_leap function with unit tests - Implemented is_leap function to determine leap years based on divisibility rules. - Added unit tests covering standard cases, edge cases, and defensive tests for invalid input types. --- solutions/is_leap_year.py | 41 +++++++++++++++ solutions/tests/test_is_leap_year.py | 74 ++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 solutions/is_leap_year.py create mode 100644 solutions/tests/test_is_leap_year.py diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py new file mode 100644 index 000000000..a29c8b4f0 --- /dev/null +++ b/solutions/is_leap_year.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for determining whether the given year is leap year or not + +Module contents: + - is_leap: determines whether the given year is leap year or not. + +Created on XX XX XX +@author: Khusro Sakhi +""" +def is_leap(year) -> bool: + """Determines if the given year is a leap year. + + Parameters: + year (int): The year to test. + + Returns: + bool: True if the year is a leap year, False otherwise. + + Raises: + AssertionError: if the argument is not an integer + AssertionError: if the argument is less than 0 + + >>> is_leap(2000) + True + + >>> is_leap(1990) + False + + >>> is_leap(2100) + False + + >>> is_leap(2004) + True + """ + assert isinstance(year, int), "entered year is not an integer" + assert year >= 0, "year is less than 0" + + # A leap year is divisible by 4, but not by 100 unless also divisible by 400 + return (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py new file mode 100644 index 000000000..644cc2d74 --- /dev/null +++ b/solutions/tests/test_is_leap_year.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for is_leap function. +Contains intentionally buggy tests for debugging practice. + +Test categories: + - Standard cases: typical lists with different lengths + - Edge cases: empty lists, single elements + - Defensive tests: wrong input types, assertions + +Created on XX XX XX + +@author: Khusro Sakhi +""" +import unittest +from ..is_leap_year import is_leap + +class TestLeapYear(unittest.TestCase): + + # Standard test cases + def test_leap_year_2000(self): + """It should return True for the year 2000 (divisible by 400)""" + self.assertTrue(is_leap(2000)) + + def test_non_leap_year_1990(self): + """It should return False for the year 1990 (not divisible by 4)""" + self.assertFalse(is_leap(1990)) + + def test_non_leap_year_2100(self): + """It should return False for the year 2100 (divisible by 100 but not by 400)""" + self.assertFalse(is_leap(2100)) + + def test_leap_year_2004(self): + """It should return True for the year 2004 (divisible by 4 but not by 100)""" + self.assertTrue(is_leap(2004)) + + # Edge cases + def test_leap_year_4(self): + """It should return True for the year 4 (the smallest leap year)""" + self.assertTrue(is_leap(4)) + + def test_non_leap_year_1(self): + """It should return False for the year 1 (not divisible by 4)""" + self.assertFalse(is_leap(1)) + + def test_non_leap_year_100(self): + """It should return False for the year 100 (divisible by 100 but not by 400)""" + self.assertFalse(is_leap(100)) + + # Defensive tests + def test_string_input(self): + """It should raise AssertionError for a string input""" + with self.assertRaises(AssertionError): + is_leap('2000') + + def test_negative_year(self): + """It should raise AssertionError for a negative year""" + with self.assertRaises(AssertionError): + is_leap(-4) + + def test_none_input(self): + """It should raise AssertionError for None input""" + with self.assertRaises(AssertionError): + is_leap(None) + + def test_float_input(self): + """It should raise AssertionError for a float input""" + with self.assertRaises(AssertionError): + is_leap(4.5) + + +if __name__ == "__main__": + unittest.main() From 2ed312d27c2812402dd5683746aaeae2180add2e Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sat, 28 Dec 2024 20:39:59 +0300 Subject: [PATCH 02/11] Fix: Removed trailing whitespaces in test file --- solutions/tests/test_is_leap_year.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py index 644cc2d74..f0357da56 100644 --- a/solutions/tests/test_is_leap_year.py +++ b/solutions/tests/test_is_leap_year.py @@ -17,53 +17,51 @@ from ..is_leap_year import is_leap class TestLeapYear(unittest.TestCase): - - # Standard test cases + """Test suite for the is_leap function - contains buggy tests!""" + + # Standard test cases def test_leap_year_2000(self): """It should return True for the year 2000 (divisible by 400)""" self.assertTrue(is_leap(2000)) - def test_non_leap_year_1990(self): """It should return False for the year 1990 (not divisible by 4)""" self.assertFalse(is_leap(1990)) - def test_non_leap_year_2100(self): """It should return False for the year 2100 (divisible by 100 but not by 400)""" self.assertFalse(is_leap(2100)) - def test_leap_year_2004(self): """It should return True for the year 2004 (divisible by 4 but not by 100)""" self.assertTrue(is_leap(2004)) - + # Edge cases def test_leap_year_4(self): """It should return True for the year 4 (the smallest leap year)""" self.assertTrue(is_leap(4)) - + def test_non_leap_year_1(self): """It should return False for the year 1 (not divisible by 4)""" self.assertFalse(is_leap(1)) - + def test_non_leap_year_100(self): """It should return False for the year 100 (divisible by 100 but not by 400)""" self.assertFalse(is_leap(100)) - + # Defensive tests def test_string_input(self): """It should raise AssertionError for a string input""" with self.assertRaises(AssertionError): is_leap('2000') - + def test_negative_year(self): """It should raise AssertionError for a negative year""" with self.assertRaises(AssertionError): is_leap(-4) - + def test_none_input(self): """It should raise AssertionError for None input""" with self.assertRaises(AssertionError): is_leap(None) - + def test_float_input(self): """It should raise AssertionError for a float input""" with self.assertRaises(AssertionError): From 9cf1cd8686e320714e6085449b1c38de56102a97 Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sat, 28 Dec 2024 22:02:29 +0300 Subject: [PATCH 03/11] Fix: Removed trailing whitespaces in solution file --- solutions/is_leap_year.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py index a29c8b4f0..82eaf847e 100644 --- a/solutions/is_leap_year.py +++ b/solutions/is_leap_year.py @@ -33,7 +33,7 @@ def is_leap(year) -> bool: >>> is_leap(2004) True - """ + """ assert isinstance(year, int), "entered year is not an integer" assert year >= 0, "year is less than 0" From cf6f18974b9ef13bde8dec60a7a08e3084b6c4a1 Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sat, 28 Dec 2024 22:44:06 +0300 Subject: [PATCH 04/11] Fix: Resolved formatting issues to pass CI checks --- solutions/__init__.py | 1 - solutions/is_leap_year.py | 4 +++- solutions/tests/__init__.py | 1 - solutions/tests/test_is_leap_year.py | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 solutions/__init__.py delete mode 100644 solutions/tests/__init__.py diff --git a/solutions/__init__.py b/solutions/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/solutions/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py index 82eaf847e..dfd64f4b6 100644 --- a/solutions/is_leap_year.py +++ b/solutions/is_leap_year.py @@ -9,6 +9,8 @@ Created on XX XX XX @author: Khusro Sakhi """ + + def is_leap(year) -> bool: """Determines if the given year is a leap year. @@ -38,4 +40,4 @@ def is_leap(year) -> bool: assert year >= 0, "year is less than 0" # A leap year is divisible by 4, but not by 100 unless also divisible by 400 - return (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) diff --git a/solutions/tests/__init__.py b/solutions/tests/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/solutions/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py index f0357da56..68cf0a2c9 100644 --- a/solutions/tests/test_is_leap_year.py +++ b/solutions/tests/test_is_leap_year.py @@ -8,14 +8,16 @@ - Standard cases: typical lists with different lengths - Edge cases: empty lists, single elements - Defensive tests: wrong input types, assertions - + Created on XX XX XX @author: Khusro Sakhi """ + import unittest from ..is_leap_year import is_leap + class TestLeapYear(unittest.TestCase): """Test suite for the is_leap function - contains buggy tests!""" @@ -23,12 +25,15 @@ class TestLeapYear(unittest.TestCase): def test_leap_year_2000(self): """It should return True for the year 2000 (divisible by 400)""" self.assertTrue(is_leap(2000)) + def test_non_leap_year_1990(self): """It should return False for the year 1990 (not divisible by 4)""" self.assertFalse(is_leap(1990)) + def test_non_leap_year_2100(self): """It should return False for the year 2100 (divisible by 100 but not by 400)""" self.assertFalse(is_leap(2100)) + def test_leap_year_2004(self): """It should return True for the year 2004 (divisible by 4 but not by 100)""" self.assertTrue(is_leap(2004)) @@ -50,7 +55,7 @@ def test_non_leap_year_100(self): def test_string_input(self): """It should raise AssertionError for a string input""" with self.assertRaises(AssertionError): - is_leap('2000') + is_leap("2000") def test_negative_year(self): """It should raise AssertionError for a negative year""" From 205aada07d88bfe7cd28e9711670ecfb54d79e9e Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sat, 28 Dec 2024 22:58:37 +0300 Subject: [PATCH 05/11] Fix --- solutions/__init__.py | 1 + solutions/tests/__init__.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 solutions/__init__.py create mode 100644 solutions/tests/__init__.py diff --git a/solutions/__init__.py b/solutions/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/solutions/__init__.py @@ -0,0 +1 @@ + diff --git a/solutions/tests/__init__.py b/solutions/tests/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/solutions/tests/__init__.py @@ -0,0 +1 @@ + From 3429b313646ca55917655f30154537a117fe2197 Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sun, 29 Dec 2024 00:16:05 +0300 Subject: [PATCH 06/11] Update: Refined test module description for is_leap function --- solutions/tests/test_is_leap_year.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py index 68cf0a2c9..cef8d7324 100644 --- a/solutions/tests/test_is_leap_year.py +++ b/solutions/tests/test_is_leap_year.py @@ -5,9 +5,9 @@ Contains intentionally buggy tests for debugging practice. Test categories: - - Standard cases: typical lists with different lengths - - Edge cases: empty lists, single elements - - Defensive tests: wrong input types, assertions + - Standard cases: Common leap and non-leap years + - Edge cases: Smallest leap year, non-leap years close to leap years + - Defensive tests: Handling of invalid inputs (e.g., non-integer, negative values) Created on XX XX XX @@ -15,6 +15,7 @@ """ import unittest + from ..is_leap_year import is_leap From db329b8e1e4124dcfb678430675277a0134087d4 Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sun, 29 Dec 2024 20:38:11 +0300 Subject: [PATCH 07/11] Renamed function to match the file name according to checklist --- solutions/is_leap_year.py | 2 +- solutions/tests/test_is_leap_year.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py index dfd64f4b6..6e1c72c61 100644 --- a/solutions/is_leap_year.py +++ b/solutions/is_leap_year.py @@ -11,7 +11,7 @@ """ -def is_leap(year) -> bool: +def is_leap_year(year) -> bool: """Determines if the given year is a leap year. Parameters: diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py index cef8d7324..052627d59 100644 --- a/solutions/tests/test_is_leap_year.py +++ b/solutions/tests/test_is_leap_year.py @@ -16,7 +16,7 @@ import unittest -from ..is_leap_year import is_leap +from ..is_leap_year import is_leap_year class TestLeapYear(unittest.TestCase): @@ -25,53 +25,53 @@ class TestLeapYear(unittest.TestCase): # Standard test cases def test_leap_year_2000(self): """It should return True for the year 2000 (divisible by 400)""" - self.assertTrue(is_leap(2000)) + self.assertTrue(is_leap_year(2000)) def test_non_leap_year_1990(self): """It should return False for the year 1990 (not divisible by 4)""" - self.assertFalse(is_leap(1990)) + self.assertFalse(is_leap_year(1990)) def test_non_leap_year_2100(self): """It should return False for the year 2100 (divisible by 100 but not by 400)""" - self.assertFalse(is_leap(2100)) + self.assertFalse(is_leap_year(2100)) def test_leap_year_2004(self): """It should return True for the year 2004 (divisible by 4 but not by 100)""" - self.assertTrue(is_leap(2004)) + self.assertTrue(is_leap_year(2004)) # Edge cases def test_leap_year_4(self): """It should return True for the year 4 (the smallest leap year)""" - self.assertTrue(is_leap(4)) + self.assertTrue(is_leap_year(4)) def test_non_leap_year_1(self): """It should return False for the year 1 (not divisible by 4)""" - self.assertFalse(is_leap(1)) + self.assertFalse(is_leap_year(1)) def test_non_leap_year_100(self): """It should return False for the year 100 (divisible by 100 but not by 400)""" - self.assertFalse(is_leap(100)) + self.assertFalse(is_leap_year(100)) # Defensive tests def test_string_input(self): """It should raise AssertionError for a string input""" with self.assertRaises(AssertionError): - is_leap("2000") + is_leap_year("2000") def test_negative_year(self): """It should raise AssertionError for a negative year""" with self.assertRaises(AssertionError): - is_leap(-4) + is_leap_year(-4) def test_none_input(self): """It should raise AssertionError for None input""" with self.assertRaises(AssertionError): - is_leap(None) + is_leap_year(None) def test_float_input(self): """It should raise AssertionError for a float input""" with self.assertRaises(AssertionError): - is_leap(4.5) + is_leap_year(4.5) if __name__ == "__main__": From 55f13921022351cd143b14981362417542bcb28f Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sun, 29 Dec 2024 20:54:51 +0300 Subject: [PATCH 08/11] Update: Refined docstring to include additional AssertionError cases --- solutions/is_leap_year.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py index 6e1c72c61..a44f00f44 100644 --- a/solutions/is_leap_year.py +++ b/solutions/is_leap_year.py @@ -20,9 +20,11 @@ def is_leap_year(year) -> bool: Returns: bool: True if the year is a leap year, False otherwise. - Raises: - AssertionError: if the argument is not an integer - AssertionError: if the argument is less than 0 + Raises: + AssertionError: If the input is a string. + AssertionError: If the input is a negative year. + AssertionError: If the input is None. + AssertionError: If the input is a float. >>> is_leap(2000) True From 267ee3ac7f0baee8d82ac744217bd74db46b1201 Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Wed, 1 Jan 2025 14:19:06 +0300 Subject: [PATCH 09/11] Refactor: Added type annotations and improved function docstrings --- solutions/is_leap_year.py | 6 +++--- solutions/tests/test_is_leap_year.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py index a44f00f44..e52fc41f9 100644 --- a/solutions/is_leap_year.py +++ b/solutions/is_leap_year.py @@ -6,12 +6,12 @@ Module contents: - is_leap: determines whether the given year is leap year or not. -Created on XX XX XX +Created on 29/12/2024 @author: Khusro Sakhi """ -def is_leap_year(year) -> bool: +def is_leap_year(year: int) -> bool: """Determines if the given year is a leap year. Parameters: @@ -39,7 +39,7 @@ def is_leap_year(year) -> bool: True """ assert isinstance(year, int), "entered year is not an integer" - assert year >= 0, "year is less than 0" + assert year > 0, "year is less than 0" # A leap year is divisible by 4, but not by 100 unless also divisible by 400 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py index 052627d59..b7780371a 100644 --- a/solutions/tests/test_is_leap_year.py +++ b/solutions/tests/test_is_leap_year.py @@ -2,14 +2,13 @@ # -*- coding: utf-8 -*- """ Test module for is_leap function. -Contains intentionally buggy tests for debugging practice. Test categories: - Standard cases: Common leap and non-leap years - Edge cases: Smallest leap year, non-leap years close to leap years - Defensive tests: Handling of invalid inputs (e.g., non-integer, negative values) -Created on XX XX XX +Created on 29/12/2024 @author: Khusro Sakhi """ From cc077e0e8e5ae08b0eff7949236396a59081e9c9 Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Wed, 1 Jan 2025 14:21:07 +0300 Subject: [PATCH 10/11] Fix: Corrected minor issue in settings.json --- .vscode/settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bbda5188d..252022b48 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -119,8 +119,8 @@ "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.ruff": true, - "source.organizeImports.ruff": true + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" } } } From b62777532b7ed6d6682428dbcbe7c5d29eac45dd Mon Sep 17 00:00:00 2001 From: Khusro-S Date: Sun, 5 Jan 2025 16:16:02 +0300 Subject: [PATCH 11/11] Refactor: Modified input validation and updated input handling in the function. - Replaced assert statements with specific error handling (TypeError and ValueError). - Updated test cases to check for the new errors: -- TypeError for non-integer inputs (string, None, float). -- ValueError for negative year inputs. --- solutions/is_leap_year.py | 38 ++++++++++++++-------------- solutions/tests/test_is_leap_year.py | 21 +++++++++------ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py index e52fc41f9..cd89c1062 100644 --- a/solutions/is_leap_year.py +++ b/solutions/is_leap_year.py @@ -14,32 +14,32 @@ def is_leap_year(year: int) -> bool: """Determines if the given year is a leap year. - Parameters: - year (int): The year to test. + Parameters: + year (int): The year to test. - Returns: - bool: True if the year is a leap year, False otherwise. + Returns: + bool: True if the year is a leap year, False otherwise. - Raises: - AssertionError: If the input is a string. - AssertionError: If the input is a negative year. - AssertionError: If the input is None. - AssertionError: If the input is a float. + Raises: + TypeError: If the input is not an integer. + ValueError: If the input is less than or equal to 0. - >>> is_leap(2000) - True + >>> is_leap_year(2000) + True - >>> is_leap(1990) - False + >>> is_leap_year(1990) + False - >>> is_leap(2100) - False + >>> is_leap_year(2100) + False - >>> is_leap(2004) - True + >>> is_leap_year(2004) + True """ - assert isinstance(year, int), "entered year is not an integer" - assert year > 0, "year is less than 0" + if not isinstance(year, int): + raise TypeError("Entered year is not an integer.") + if year <= 0: + raise ValueError("Year must be greater than 0.") # A leap year is divisible by 4, but not by 100 unless also divisible by 400 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py index b7780371a..0adac81a9 100644 --- a/solutions/tests/test_is_leap_year.py +++ b/solutions/tests/test_is_leap_year.py @@ -53,23 +53,28 @@ def test_non_leap_year_100(self): # Defensive tests def test_string_input(self): - """It should raise AssertionError for a string input""" - with self.assertRaises(AssertionError): + """It should raise TypeError for a string input""" + with self.assertRaises(TypeError): is_leap_year("2000") def test_negative_year(self): - """It should raise AssertionError for a negative year""" - with self.assertRaises(AssertionError): + """It should raise ValueError for a negative year""" + with self.assertRaises(ValueError): is_leap_year(-4) def test_none_input(self): - """It should raise AssertionError for None input""" - with self.assertRaises(AssertionError): + """It should raise TypeError for None input""" + with self.assertRaises(TypeError): is_leap_year(None) + def test_zero_input(self): + """It should raise ValueError for 0 input""" + with self.assertRaises(ValueError): + is_leap_year(0) + def test_float_input(self): - """It should raise AssertionError for a float input""" - with self.assertRaises(AssertionError): + """It should raise TypeError for a float input""" + with self.assertRaises(TypeError): is_leap_year(4.5)