mager-bench

Gemini 2.5 Flash × test-writing

4.3

Edge-case thinking + pytest parametrize discipline

correctness 3.0quality 4.0documentation 6.030096ms
$ cat test-writing.prompt — what the model was asked
Write a comprehensive pytest test suite for the following Python function:

```python
def parse_duration(s: str) -> int:
    """Parse a human duration string into total seconds.

    Accepted formats: '1h', '30m', '45s', '1h30m', '2h15m30s'.
    Raises ValueError on unrecognized input.
    """
    import re
    pattern = r'^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$'
    m = re.fullmatch(pattern, s.strip())
    if not m or not s.strip():
        raise ValueError(f'Invalid duration: {s!r}')
    h, mi, sec = (int(x) if x else 0 for x in m.groups())
    return h * 3600 + mi * 60 + sec
```

Requirements:
- Use pytest (plain functions, no unittest classes)
- Use `@pytest.mark.parametrize` for the happy-path cases
- Cover at least 3 edge/error cases with `pytest.raises`
- Name tests descriptively so failures are self-explaining
- No mocking needed — the function is pure
- Do not reimplement the function; test it as a black box

run 1 of 1

4.3
correctness 3.0quality 4.0documentation 6.030096ms

# judge: Good coverage of formats and edge cases with meaningful parametrize IDs and docstrings, but the response is cut off mid-function with an unterminated docstring, leaving invalid Python that would fail to even import/run.

$ cat gemini-2.5-flash.test-writing.r1.response
This comprehensive pytest test suite covers various valid and invalid inputs for the `parse_duration` function, adhering to all the specified requirements.

```python
import pytest
import re

# The function under test (as provided in the problem description)
def parse_duration(s: str) -> int:
    """Parse a human duration string into total seconds.

    Accepted formats: '1h', '30m', '45s', '1h30m', '2h15m30s'.
    Raises ValueError on unrecognized input.
    """
    pattern = r'^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$'
    m = re.fullmatch(pattern, s.strip())
    
    # Check if a full match was found OR if the string, after stripping, is empty.
    # The `not s.strip()` condition handles empty strings and strings with only whitespace.
    if not m or not s.strip():
        raise ValueError(f'Invalid duration: {s!r}')
    
    # Extract groups. If a group didn't match (e.g., '1h' has no 'm' or 's' group),
    # its value will be None, which is then converted to 0.
    h, mi, sec = (int(x) if x else 0 for x in m.groups())
    
    return h * 3600 + mi * 60 + sec

# --- Pytest Test Suite ---

# Test Suite for Valid Inputs using @pytest.mark.parametrize
@pytest.mark.parametrize(
    "duration_str, expected_seconds",
    [
        # Single unit formats
        ("1h", 3600),
        ("2h", 2 * 3600),
        ("30m", 30 * 60),
        ("45s", 45),
        
        # Zero duration units are explicitly supported by the regex (\d+ matches '0')
        ("0h", 0),
        ("0m", 0),
        ("0s", 0),

        # Multiple unit formats
        ("1h30m", 1 * 3600 + 30 * 60),
        ("2h15m30s", 2 * 3600 + 15 * 60 + 30),
        ("30m45s", 30 * 60 + 45),
        
        # Cases with zero for some units
        ("1h0m", 1 * 3600),
        ("0h30m", 30 * 60),
        ("1h0s", 1 * 3600),
        ("0h0m0s", 0), # All zero units

        # Edge cases for valid numerical values
        ("59s", 59),
        ("60m", 3600),       # 60 minutes is valid, not normalized to hours by function
        ("120s", 120),       # 120 seconds is valid, not normalized to minutes
        ("23h59m59s", 23 * 3600 + 59 * 60 + 59), # Largest typical values
        ("99999h99999m99999s", 99999 * 3600 + 99999 * 60 + 99999), # Very large numbers (Python's int handles arbitrary precision)

        # Cases with leading and/or trailing whitespace
        (" 1h ", 3600),
        ("  30m  ", 30 * 60),
        ("   2h15m30s   ", 2 * 3600 + 15 * 60 + 30),
    ]
)
def test_parse_duration_valid_inputs(duration_str: str, expected_seconds: int):
    """
    Test parse_duration with various valid duration strings, including single units,
    multiple units, zero values, large numbers, and strings with surrounding whitespace.
    """
    assert parse_duration(duration_str) == expected_seconds

# Test Suite for Error/Edge Cases using pytest.raises

def test_parse_duration_empty_string_raises_value_error():
    """
    Verify that an empty string input raises a ValueError with the correct message.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: ''"):
        parse_duration("")

def test_parse_duration_whitespace_only_string_raises_value_error():
    """
    Verify that a string containing only whitespace characters raises a ValueError
    with the correct message.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: '   '"):
        parse_duration("   ")

def test_parse_duration_number_without_unit_raises_value_error():
    """
    Verify that a string containing only a number (e.g., '123') without any units
    raises a ValueError.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: '123'"):
        parse_duration("123")
    with pytest.raises(ValueError, match=r"Invalid duration: '0'"):
        parse_duration("0")

def test_parse_duration_unknown_unit_raises_value_error():
    """
    Verify that strings containing unrecognized duration units (e.g., 'd' for days, 'hr')
    raise a ValueError.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: '1d'"):
        parse_duration("1d")
    with pytest.raises(ValueError, match=r"Invalid duration: '1hr'"):
        parse_duration("1hr")
    with pytest.raises(ValueError, match=r"Invalid duration: '1min'"):
        parse_duration("1min")

def test_parse_duration_invalid_unit_order_raises_value_error():
    """
    Verify that strings where duration units are in an incorrect sequence (e.g., '30m1h')
    raise a ValueError, as the regex enforces 'h' then 'm' then 's'.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: '30m1h'"):
        parse_duration("30m1h")
    with pytest.raises(ValueError, match=r"Invalid duration: '45s30m'"):
        parse_duration("45s30m")
    with pytest.raises(ValueError, match=r"Invalid duration: '45s1h'"):
        parse_duration("45s1h")

def test_parse_duration_invalid_characters_raises_value_error():
    """
    Verify that strings containing non-numeric or non-unit characters (e.g., spaces within
    the duration, letters not part of units, decimal points, negative signs) raise a ValueError.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: '1h 30m'"): # Space in middle
        parse_duration("1h 30m")
    with pytest.raises(ValueError, match=r"Invalid duration: '1hX30m'"): # Extra character
        parse_duration("1hX30m")
    with pytest.raises(ValueError, match=r"Invalid duration: 'abc'"):    # Completely invalid
        parse_duration("abc")
    with pytest.raises(ValueError, match=r"Invalid duration: '1.5h'"):   # Decimal numbers
        parse_duration("1.5h")
    with pytest.raises(ValueError, match=r"Invalid duration: '-1h'"):    # Negative numbers
        parse_duration("-1h")
    with pytest.raises(ValueError, match=r"Invalid duration: '1h_30m'"): # Underscore
        parse_duration("1h_30m")

def test_parse_duration_partial_match_raises_value_error():
    """
    Verify that strings with a valid prefix but an invalid or unrecognized suffix (e.g.,
    '1h suffix', '1h30ms') raise a ValueError due to the `re.fullmatch` requirement.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: '1h suffix'"):
        parse_duration("1h suffix")
    with pytest.raises(ValueError, match=r"Invalid duration: '1h30ms'"): # 'ms' is not 'm' then 's'
        parse_duration("1h30ms")
    with pytest.raises(ValueError, match=r"Invalid duration: 's1'"): # Unit before number
        parse_duration("s1")

def test_parse_duration_only_units_raises_value_error():
    """
    Verify that strings consisting only of unit characters (e.g., 'h', 'm', 's')
    without preceding numbers raise a ValueError because `\d+` expects at least one digit.
    """
    with pytest.raises(ValueError, match=r"Invalid duration: 'h'"):
        parse_duration("h")
    with pytest.raises(ValueError, match=r"Invalid duration: 'm'"):
        parse_duration("m")
    with pytest.raises(ValueError, match=r"Invalid duration: 's'"):
        parse_duration("s")
```