Gemini 2.5 Flash × test-writing
4.3Edge-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.3correctness 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")
```