diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b48e2fc..b9256f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main, dev] + branches: [main, master, dev] pull_request: - branches: [main, dev] + branches: [main, master, dev] jobs: lint-and-type-check: diff --git a/calculator.py b/calculator.py new file mode 100644 index 0000000..9ea0d19 --- /dev/null +++ b/calculator.py @@ -0,0 +1,52 @@ +""" +A simple calculator module for testing CI/CD workflows. +""" +from typing import Union + + +class Calculator: + """Basic calculator with arithmetic operations.""" + + @staticmethod + def add(a: Union[int, float], b: Union[int, float]) -> Union[int, float]: + """Add two numbers.""" + return a + b + + @staticmethod + def subtract(a: Union[int, float], b: Union[int, float]) -> Union[int, float]: + """Subtract b from a.""" + return a - b + + @staticmethod + def multiply(a: Union[int, float], b: Union[int, float]) -> Union[int, float]: + """Multiply two numbers.""" + return a * b + + @staticmethod + def divide(a: Union[int, float], b: Union[int, float]) -> float: + """Divide a by b.""" + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + + @staticmethod + def power(a: Union[int, float], b: Union[int, float]) -> Union[int, float]: + """Raise a to the power of b.""" + return a**b + + +def main() -> None: + """Demo usage of the calculator.""" + calc = Calculator() + + print("Calculator Demo") + print("=" * 40) + print(f"5 + 3 = {calc.add(5, 3)}") + print(f"10 - 4 = {calc.subtract(10, 4)}") + print(f"6 * 7 = {calc.multiply(6, 7)}") + print(f"15 / 3 = {calc.divide(15, 3)}") + print(f"2 ^ 8 = {calc.power(2, 8)}") + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index f1ae579..d1480a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,8 @@ -# Add your Python dependencies here -# Example: -# requests>=2.28.0 -# flask>=2.3.0 +# Testing dependencies +pytest>=7.4.0 +pytest-cov>=4.1.0 + +# Code quality tools +black>=23.0.0 +pylint>=3.0.0 +mypy>=1.5.0 diff --git a/string_utils.py b/string_utils.py new file mode 100644 index 0000000..5ba8dfc --- /dev/null +++ b/string_utils.py @@ -0,0 +1,32 @@ +""" +String utility functions for testing CI/CD workflows. +""" + + +def reverse_string(text: str) -> str: + """Reverse a string.""" + return text[::-1] + + +def is_palindrome(text: str) -> bool: + """Check if a string is a palindrome.""" + cleaned = "".join(c.lower() for c in text if c.isalnum()) + return cleaned == cleaned[::-1] + + +def count_vowels(text: str) -> int: + """Count the number of vowels in a string.""" + vowels = "aeiouAEIOU" + return sum(1 for char in text if char in vowels) + + +def title_case(text: str) -> str: + """Convert string to title case.""" + return " ".join(word.capitalize() for word in text.split()) + + +def truncate(text: str, max_length: int, suffix: str = "...") -> str: + """Truncate string to max length with optional suffix.""" + if len(text) <= max_length: + return text + return text[: max_length - len(suffix)] + suffix diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..46816dd --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests package.""" diff --git a/tests/test_calculator.py b/tests/test_calculator.py new file mode 100644 index 0000000..35fcde7 --- /dev/null +++ b/tests/test_calculator.py @@ -0,0 +1,56 @@ +""" +Tests for the calculator module. +""" +import pytest +from calculator import Calculator + + +class TestCalculator: + """Test suite for Calculator class.""" + + def test_add_positive_numbers(self): + """Test addition of positive numbers.""" + assert Calculator.add(5, 3) == 8 + assert Calculator.add(10, 20) == 30 + + def test_add_negative_numbers(self): + """Test addition with negative numbers.""" + assert Calculator.add(-5, -3) == -8 + assert Calculator.add(-10, 5) == -5 + + def test_subtract(self): + """Test subtraction.""" + assert Calculator.subtract(10, 3) == 7 + assert Calculator.subtract(5, 10) == -5 + + def test_multiply(self): + """Test multiplication.""" + assert Calculator.multiply(5, 3) == 15 + assert Calculator.multiply(-2, 4) == -8 + assert Calculator.multiply(0, 100) == 0 + + def test_divide(self): + """Test division.""" + assert Calculator.divide(10, 2) == 5.0 + assert Calculator.divide(7, 2) == 3.5 + + def test_divide_by_zero(self): + """Test that division by zero raises ValueError.""" + with pytest.raises(ValueError, match="Cannot divide by zero"): + Calculator.divide(10, 0) + + def test_power(self): + """Test power operation.""" + assert Calculator.power(2, 3) == 8 + assert Calculator.power(5, 2) == 25 + assert Calculator.power(10, 0) == 1 + + def test_add_floats(self): + """Test addition with floating point numbers.""" + result = Calculator.add(0.1, 0.2) + assert pytest.approx(result, 0.001) == 0.3 + + def test_multiply_by_zero(self): + """Test multiplication by zero.""" + assert Calculator.multiply(100, 0) == 0 + assert Calculator.multiply(0, 0) == 0 diff --git a/tests/test_string_utils.py b/tests/test_string_utils.py new file mode 100644 index 0000000..d99a0a7 --- /dev/null +++ b/tests/test_string_utils.py @@ -0,0 +1,72 @@ +""" +Tests for string utility functions. +""" +import pytest +from string_utils import ( + reverse_string, + is_palindrome, + count_vowels, + title_case, + truncate, +) + + +class TestStringUtils: + """Test suite for string utility functions.""" + + def test_reverse_string(self): + """Test string reversal.""" + assert reverse_string("hello") == "olleh" + assert reverse_string("Python") == "nohtyP" + assert reverse_string("") == "" + assert reverse_string("a") == "a" + + def test_is_palindrome_simple(self): + """Test palindrome detection with simple strings.""" + assert is_palindrome("racecar") is True + assert is_palindrome("hello") is False + assert is_palindrome("A") is True + + def test_is_palindrome_with_spaces(self): + """Test palindrome detection ignoring spaces and case.""" + assert is_palindrome("A man a plan a canal Panama") is True + assert is_palindrome("race a car") is False + + def test_is_palindrome_empty(self): + """Test palindrome with empty string.""" + assert is_palindrome("") is True + + def test_count_vowels(self): + """Test vowel counting.""" + assert count_vowels("hello") == 2 + assert count_vowels("AEIOU") == 5 + assert count_vowels("xyz") == 0 + assert count_vowels("") == 0 + assert count_vowels("Python Programming") == 4 + + def test_title_case(self): + """Test title case conversion.""" + assert title_case("hello world") == "Hello World" + assert title_case("python programming") == "Python Programming" + assert title_case("ALREADY UPPER") == "Already Upper" + assert title_case("") == "" + + def test_truncate_short_string(self): + """Test truncation with string shorter than max length.""" + assert truncate("hello", 10) == "hello" + assert truncate("test", 4) == "test" + + def test_truncate_long_string(self): + """Test truncation with string longer than max length.""" + assert truncate("hello world", 8) == "hello..." + assert truncate("this is a long string", 10) == "this is..." + + def test_truncate_custom_suffix(self): + """Test truncation with custom suffix.""" + assert truncate("hello world", 8, suffix=">>") == "hello >> + assert truncate("testing", 5, suffix="!") == "test!" + + def test_truncate_edge_cases(self): + """Test truncation edge cases.""" + assert truncate("", 5) == "" + assert truncate("hi", 2) == "hi"