{
 "trial": "Do AI-written test suites actually catch planted bugs",
 "claude_code": "2.1.226",
 "date": "2026-08-10",
 "method": "For each function the model was shown only the source and asked for a pytest suite. The suite must pass the correct implementation before it is scored, then it is run against deliberately broken versions and each mutant counts as killed if the suite fails.",
 "controls": {
  "equivalent_mutants": "Each mutant is run against the original over a probe set; one that behaves identically on every probe is uncatchable and excluded. 1 of 12 was excluded on that basis.",
  "suite_must_pass_original": "A suite failing the correct code is recorded unusable rather than scored, because its catches would be meaningless.",
  "suite_must_have_tests": "A file collecting zero tests cannot count as passing.",
  "apparatus": "A hand-written thorough suite killed 3 of 3 clamp mutants and a hand-written trivial suite killed 0 of 3, both passing the original, so the apparatus discriminates."
 },
 "subjects": {
  "clamp": {
   "source": "\ndef clamp(value, low, high):\n    if low > high:\n        raise ValueError(\"low must not exceed high\")\n    if value < low:\n        return low\n    if value > high:\n        return high\n    return value\n",
   "mutants": {
    "boundary_off_by_one": "\ndef clamp(value, low, high):\n    if low > high:\n        raise ValueError(\"low must not exceed high\")\n    if value < low:\n        return low\n    if value > high + 1:\n        return high\n    return value\n",
    "drops_validation": "\ndef clamp(value, low, high):\n    if value < low:\n        return low\n    if value > high:\n        return high\n    return value\n",
    "swapped_bounds": "\ndef clamp(value, low, high):\n    if low > high:\n        raise ValueError(\"low must not exceed high\")\n    if value < low:\n        return high\n    if value > high:\n        return low\n    return value\n"
   }
  },
  "parse_range": {
   "source": "\ndef parse_range(text):\n    parts = text.split(\"-\")\n    if len(parts) != 2:\n        raise ValueError(\"expected two parts\")\n    start, end = int(parts[0]), int(parts[1])\n    if start > end:\n        raise ValueError(\"start after end\")\n    return (start, end)\n",
   "mutants": {
    "no_order_check": "\ndef parse_range(text):\n    parts = text.split(\"-\")\n    if len(parts) != 2:\n        raise ValueError(\"expected two parts\")\n    return (int(parts[0]), int(parts[1]))\n",
    "inclusive_off_by_one": "\ndef parse_range(text):\n    parts = text.split(\"-\")\n    if len(parts) != 2:\n        raise ValueError(\"expected two parts\")\n    start, end = int(parts[0]), int(parts[1])\n    if start > end:\n        raise ValueError(\"start after end\")\n    return (start, end + 1)\n"
   }
  },
  "chunk": {
   "source": "\ndef chunk(items, size):\n    if size <= 0:\n        raise ValueError(\"size must be positive\")\n    return [items[i:i + size] for i in range(0, len(items), size)]\n",
   "mutants": {
    "drops_remainder": "\ndef chunk(items, size):\n    if size <= 0:\n        raise ValueError(\"size must be positive\")\n    out = [items[i:i + size] for i in range(0, len(items), size)]\n    return [c for c in out if len(c) == size]\n",
    "allows_zero_size": "\ndef chunk(items, size):\n    if size < 0:\n        raise ValueError(\"size must be positive\")\n    if size == 0:\n        return []\n    return [items[i:i + size] for i in range(0, len(items), size)]\n"
   }
  },
  "normalise_spaces": {
   "source": "\ndef normalise_spaces(text):\n    return \" \".join(text.split())\n",
   "mutants": {
    "only_strips_ends": "\ndef normalise_spaces(text):\n    return text.strip()\n",
    "collapses_but_keeps_tabs": "\ndef normalise_spaces(text):\n    return \" \".join(text.split(\" \")).strip()\n"
   }
  },
  "percent_change": {
   "source": "\ndef percent_change(old, new):\n    if old == 0:\n        raise ZeroDivisionError(\"old must not be zero\")\n    return (new - old) / old * 100\n",
   "mutants": {
    "no_zero_guard": "\ndef percent_change(old, new):\n    return (new - old) / old * 100\n",
    "wrong_denominator": "\ndef percent_change(old, new):\n    if old == 0:\n        raise ZeroDivisionError(\"old must not be zero\")\n    return (new - old) / new * 100\n",
    "forgets_percent": "\ndef percent_change(old, new):\n    if old == 0:\n        raise ZeroDivisionError(\"old must not be zero\")\n    return (new - old) / old\n"
   }
  }
 },
 "runs": [
  {
   "round": "r1",
   "model": "claude-haiku-4-5-20251001",
   "subject": "clamp",
   "status": "ok",
   "tests": 9,
   "mutants": 3,
   "killed": [
    "boundary_off_by_one",
    "drops_validation",
    "swapped_bounds"
   ],
   "survived": [],
   "kill_count": 3,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\nfrom subject import clamp\n\n\ndef test_clamp_value_within_range():\n    \"\"\"Test clamping a value that is already within range.\"\"\"\n    assert clamp(5, 0, 10) == 5\n\n\ndef test_clamp_value_below_low():\n    \"\"\"Test clamping a value below the low bound.\"\"\"\n    assert clamp(-5, 0, 10) == 0\n\n\ndef test_clamp_value_above_high():\n    \"\"\"Test clamping a value above the high bound.\"\"\"\n    assert clamp(15, 0, 10) == 10\n\n\ndef test_clamp_value_equals_low():\n    \"\"\"Test clamping a value equal to low.\"\"\"\n    assert clamp(0, 0, 10) == 0\n\n\ndef test_clamp_value_equals_high():\n    \"\"\"Test clamping a value equal to high.\"\"\"\n    assert clamp(10, 0, 10) == 10\n\n\ndef test_clamp_low_equals_high():\n    \"\"\"Test clamping when low equals high.\"\"\"\n    assert clamp(5, 5, 5) == 5\n    assert clamp(10, 5, 5) == 5\n    assert clamp(0, 5, 5) == 5\n\n\ndef test_clamp_with_negative_numbers():\n    \"\"\"Test clamping with negative bounds.\"\"\"\n    assert clamp(-5, -10, -1) == -5\n    assert clamp(-15, -10, -1) == -10\n    assert clamp(0, -10, -1) == -1\n\n\ndef test_clamp_with_floats():\n    \"\"\"Test clamping with floating point numbers.\"\"\"\n    assert clamp(5.5, 0.0, 10.0) == 5.5\n    assert clamp(-0.5, 0.0, 10.0) == 0.0\n    assert clamp(10.5, 0.0, 10.0) == 10.0\n\n\ndef test_clamp_low_exceeds_high():\n    \"\"\"Test that ValueError is raised when low > high.\"\"\"\n    with pytest.raises(ValueError, match=\"low must not exceed high\"):\n        clamp(5, 10, 0)\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-haiku-4-5-20251001",
   "subject": "parse_range",
   "status": "fails_original",
   "tests": null,
   "mutants": null,
   "killed": null,
   "survived": null,
   "kill_count": null,
   "tests_passed": 15,
   "tests_failed": 1,
   "test_src": "import pytest\nfrom subject import parse_range\n\ndef test_valid_simple_range():\n    assert parse_range(\"1-5\") == (1, 5)\n\ndef test_valid_equal_range():\n    assert parse_range(\"5-5\") == (5, 5)\n\ndef test_valid_larger_range():\n    assert parse_range(\"10-100\") == (10, 100)\n\ndef test_valid_zero_start():\n    assert parse_range(\"0-10\") == (0, 10)\n\ndef test_valid_large_numbers():\n    assert parse_range(\"1000-2000\") == (1000, 2000)\n\ndef test_error_no_dash():\n    with pytest.raises(ValueError, match=\"expected two parts\"):\n        parse_range(\"123\")\n\ndef test_error_multiple_dashes():\n    with pytest.raises(ValueError, match=\"expected two parts\"):\n        parse_range(\"1-2-3\")\n\ndef test_error_empty_string():\n    with pytest.raises(ValueError, match=\"expected two parts\"):\n        parse_range(\"\")\n\ndef test_error_only_dash():\n    with pytest.raises(ValueError):\n        parse_range(\"-\")\n\ndef test_error_trailing_dash():\n    with pytest.raises(ValueError):\n        parse_range(\"1-\")\n\ndef test_error_leading_dash():\n    with pytest.raises(ValueError, match=\"expected two parts\"):\n        parse_range(\"-1\")\n\ndef test_error_non_integer_start():\n    with pytest.raises(ValueError):\n        parse_range(\"a-5\")\n\ndef test_error_non_integer_end():\n    with pytest.raises(ValueError):\n        parse_range(\"1-b\")\n\ndef test_error_float_numbers():\n    with pytest.raises(ValueError):\n        parse_range(\"1.5-2.5\")\n\ndef test_error_start_greater_than_end():\n    with pytest.raises(ValueError, match=\"start after end\"):\n        parse_range(\"10-5\")\n\ndef test_whitespace_around_dash():\n    assert parse_range(\"1 - 5\") == (1, 5)\n",
   "detail": "^\nE       AssertionError: Regex pattern did not match.\nE         Expected regex: 'expected two parts'\nE         Actual message: \"invalid literal for int() with base 10: ''\"\n\ntest_subject.py:40: AssertionError\n=========================== short test summary info ============================\nFAILED test_subject.py::test_error_leading_dash - AssertionError: Regex patte...\n1 failed, 15 passed in 0.03s\n"
  },
  {
   "round": "r1",
   "model": "claude-haiku-4-5-20251001",
   "subject": "chunk",
   "status": "ok",
   "tests": 12,
   "mutants": 2,
   "killed": [
    "drops_remainder",
    "allows_zero_size"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\nfrom subject import chunk\n\n\nclass TestChunk:\n    def test_chunk_equal_parts(self):\n        \"\"\"Test chunking into equal parts.\"\"\"\n        assert chunk([1, 2, 3, 4, 5, 6], 2) == [[1, 2], [3, 4], [5, 6]]\n\n    def test_chunk_unequal_last(self):\n        \"\"\"Test chunking where last chunk is smaller.\"\"\"\n        assert chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4], [5]]\n\n    def test_chunk_size_one(self):\n        \"\"\"Test chunking with size=1.\"\"\"\n        assert chunk([1, 2, 3], 1) == [[1], [2], [3]]\n\n    def test_chunk_size_equals_length(self):\n        \"\"\"Test chunking with size equal to list length.\"\"\"\n        assert chunk([1, 2, 3], 3) == [[1, 2, 3]]\n\n    def test_chunk_size_greater_than_length(self):\n        \"\"\"Test chunking with size greater than list length.\"\"\"\n        assert chunk([1, 2, 3], 10) == [[1, 2, 3]]\n\n    def test_chunk_empty_list(self):\n        \"\"\"Test chunking an empty list.\"\"\"\n        assert chunk([], 2) == []\n\n    def test_chunk_single_element(self):\n        \"\"\"Test chunking a single element.\"\"\"\n        assert chunk([1], 1) == [[1]]\n\n    def test_chunk_zero_size_raises(self):\n        \"\"\"Test that size=0 raises ValueError.\"\"\"\n        with pytest.raises(ValueError, match=\"size must be positive\"):\n            chunk([1, 2, 3], 0)\n\n    def test_chunk_negative_size_raises(self):\n        \"\"\"Test that negative size raises ValueError.\"\"\"\n        with pytest.raises(ValueError, match=\"size must be positive\"):\n            chunk([1, 2, 3], -5)\n\n    def test_chunk_strings(self):\n        \"\"\"Test chunking a list of strings.\"\"\"\n        assert chunk(['a', 'b', 'c', 'd'], 2) == [['a', 'b'], ['c', 'd']]\n\n    def test_chunk_mixed_types(self):\n        \"\"\"Test chunking mixed types.\"\"\"\n        assert chunk([1, 'a', 2.5, None, True], 2) == [[1, 'a'], [2.5, None], [True]]\n\n    def test_chunk_large_size(self):\n        \"\"\"Test chunking with large size on small list.\"\"\"\n        assert chunk([1], 100) == [[1]]\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-haiku-4-5-20251001",
   "subject": "normalise_spaces",
   "status": "ok",
   "tests": 13,
   "mutants": 2,
   "killed": [
    "only_strips_ends",
    "collapses_but_keeps_tabs"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\nfrom subject import normalise_spaces\n\n\ndef test_single_space_unchanged():\n    assert normalise_spaces(\"hello world\") == \"hello world\"\n\n\ndef test_multiple_spaces_collapsed():\n    assert normalise_spaces(\"hello    world\") == \"hello world\"\n\n\ndef test_leading_spaces_removed():\n    assert normalise_spaces(\"   hello world\") == \"hello world\"\n\n\ndef test_trailing_spaces_removed():\n    assert normalise_spaces(\"hello world   \") == \"hello world\"\n\n\ndef test_leading_and_trailing_spaces_removed():\n    assert normalise_spaces(\"   hello world   \") == \"hello world\"\n\n\ndef test_tabs_converted_to_spaces():\n    assert normalise_spaces(\"hello\\tworld\") == \"hello world\"\n\n\ndef test_newlines_converted_to_spaces():\n    assert normalise_spaces(\"hello\\nworld\") == \"hello world\"\n\n\ndef test_mixed_whitespace():\n    assert normalise_spaces(\"hello  \\t\\n  world\") == \"hello world\"\n\n\ndef test_empty_string():\n    assert normalise_spaces(\"\") == \"\"\n\n\ndef test_single_word():\n    assert normalise_spaces(\"hello\") == \"hello\"\n\n\ndef test_only_whitespace():\n    assert normalise_spaces(\"     \") == \"\"\n\n\ndef test_multiple_words_with_excessive_spaces():\n    assert normalise_spaces(\"one   two   three   four\") == \"one two three four\"\n\n\ndef test_single_space_preserved():\n    assert normalise_spaces(\"a b c\") == \"a b c\"\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-haiku-4-5-20251001",
   "subject": "percent_change",
   "status": "suite_no_tests",
   "tests": null,
   "mutants": null,
   "killed": null,
   "survived": null,
   "kill_count": null,
   "tests_passed": 0,
   "tests_failed": 1,
   "test_src": "import pytest\nfrom subject import percent_change\n\n\ndef test_positive_change():\n    assert percent_change(100, 150) == 50.0\n\n\ndef test_negative_change():\n    assert percent_change(100, 50) == -50.0\n\n\ndef test_no_change():\n    assert percent_change(100, 100) == 0.0\n\n\ndef test_zero_old_raises_error():\n    with pytest.raises(ZeroDivisionError, match=\"old must not be zero\"):\n        percent_change(0, 100)\n\n\ndef test_with_floats():\n    assert percent_change(50.5, 60.6) == pytest.approx(19.801980198...)\n\n\ndef test_negative_old():\n    assert percent_change(-100, -50) == -50.0\n\n\ndef test_small_values():\n    assert percent_change(0.01, 0.02) == 100.0\n\n\ndef test_large_values():\n    assert percent_change(1000000, 1500000) == 50.0\n\n\ndef test_new_is_zero():\n    assert percent_change(100, 0) == -100.0\n\n\ndef test_from_negative_to_positive():\n    assert percent_change(-50, 50) == -200.0\n",
   "detail": "t percent_change(50.5, 60.6) == pytest.approx(19.801980198...)\nE                                                          ^^^^^^^^^^^^^^^\nE   SyntaxError: invalid syntax. Perhaps you forgot a comma?\n=========================== short test summary info ============================\nERROR test_subject.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n"
  },
  {
   "round": "r1",
   "model": "claude-opus-5",
   "subject": "clamp",
   "status": "ok",
   "tests": 350,
   "mutants": 3,
   "killed": [
    "boundary_off_by_one",
    "drops_validation",
    "swapped_bounds"
   ],
   "survived": [],
   "kill_count": 3,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import math\nfrom datetime import date\nfrom decimal import Decimal\nfrom fractions import Fraction\n\nimport pytest\n\nfrom subject import clamp\n\n\nclass Boxed:\n    def __init__(self, n, tag=\"\"):\n        self.n = n\n        self.tag = tag\n\n    def __lt__(self, other):\n        return self.n < other.n\n\n    def __gt__(self, other):\n        return self.n > other.n\n\n    def __repr__(self):\n        return \"Boxed({!r}, {!r})\".format(self.n, self.tag)\n\n\nclass Explosive:\n    def __lt__(self, other):\n        raise AssertionError(\"value was compared despite an invalid range\")\n\n    def __gt__(self, other):\n        raise AssertionError(\"value was compared despite an invalid range\")\n\n\nclass TestValueInsideRange:\n    @pytest.mark.parametrize(\n        (\"value\", \"low\", \"high\"),\n        [\n            (5, 0, 10),\n            (1, 0, 2),\n            (-5, -10, -1),\n            (0, -10, 10),\n            (2.5, 0.0, 5.0),\n            (-0.25, -1.0, 1.0),\n        ],\n    )\n    def test_strictly_inside_bounds_returns_value(self, value, low, high):\n        assert clamp(value, low, high) == value\n\n    @pytest.mark.parametrize(\n        (\"value\", \"low\", \"high\"),\n        [\n            (0, 0, 10),\n            (10, 0, 10),\n            (-10, -10, -1),\n            (-1, -10, -1),\n            (0.0, 0.0, 5.0),\n            (5.0, 0.0, 5.0),\n        ],\n    )\n    def test_value_exactly_on_a_bound_returns_value(self, value, low, high):\n        assert clamp(value, low, high) == value\n\n    @pytest.mark.parametrize(\"bound\", [0, 7, -3, 2.5])\n    def test_degenerate_range_returns_the_only_allowed_value(self, bound):\n        assert clamp(bound, bound, bound) == bound\n\n\nclass TestValueBelowRange:\n    @pytest.mark.parametrize(\n        (\"value\", \"low\", \"high\", \"expected\"),\n        [\n            (-1, 0, 10, 0),\n            (-1000, 0, 10, 0),\n            (-100, -10, -1, -10),\n            (0, 5, 5, 5),\n            (-0.5, 0.0, 1.0, 0.0),\n            (0.999, 1.0, 2.0, 1.0),\n        ],\n    )\n    def test_returns_low(self, value, low, high, expected):\n        assert clamp(value, low, high) == expected\n\n\nclass TestValueAboveRange:\n    @pytest.mark.parametrize(\n        (\"value\", \"low\", \"high\", \"expected\"),\n        [\n            (11, 0, 10, 10),\n            (1000, 0, 10, 10),\n            (0, -10, -1, -1),\n            (10, 5, 5, 5),\n            (1.5, 0.0, 1.0, 1.0),\n            (2.001, 1.0, 2.0, 2.0),\n        ],\n    )\n    def test_returns_high(self, value, low, high, expected):\n        assert clamp(value, low, high) == expected\n\n\nclass TestReturnedObjectIdentity:\n    def test_in_range_returns_the_value_object(self):\n        value, low, high = Boxed(5, \"value\"), Boxed(0, \"low\"), Boxed(10, \"high\")\n        assert clamp(value, low, high) is value\n\n    def test_value_equal_to_low_returns_the_value_not_the_bound(self):\n        value, low, high = Boxed(0, \"value\"), Boxed(0, \"low\"), Boxed(10, \"high\")\n        assert clamp(value, low, high) is value\n\n    def test_value_equal_to_high_returns_the_value_not_the_bound(self):\n        value, low, high = Boxed(10, \"value\"), Boxed(0, \"low\"), Boxed(10, \"high\")\n        assert clamp(value, low, high) is value\n\n    def test_below_range_returns_the_low_object(self):\n        value, low, high = Boxed(-5, \"value\"), Boxed(0, \"low\"), Boxed(10, \"high\")\n        assert clamp(value, low, high) is low\n\n    def test_above_range_returns_the_high_object(self):\n        value, low, high = Boxed(50, \"value\"), Boxed(0, \"low\"), Boxed(10, \"high\")\n        assert clamp(value, low, high) is high\n\n    def test_degenerate_range_returns_the_value_object(self):\n        value, bound = Boxed(3, \"value\"), Boxed(3, \"bound\")\n        assert clamp(value, bound, bound) is value\n\n\nclass TestInvalidRange:\n    @pytest.mark.parametrize(\n        (\"low\", \"high\"),\n        [\n            (1, 0),\n            (10, -10),\n            (0, -1),\n            (0.1, 0.0),\n            (1.0000001, 1.0),\n            (math.inf, 10),\n            (0, -math.inf),\n        ],\n    )\n    def test_low_greater_than_high_raises_value_error(self, low, high):\n        with pytest.raises(ValueError, match=\"low must not exceed high\"):\n            clamp(0, low, high)\n\n    def test_error_message_is_exact(self):\n        with pytest.raises(ValueError) as excinfo:\n            clamp(0, 1, -1)\n        assert str(excinfo.value) == \"low must not exceed high\"\n\n    @pytest.mark.parametrize(\"value\", [-1000, 1, 3, 5, 1000])\n    def test_raises_regardless_of_where_the_value_sits(self, value):\n        with pytest.raises(ValueError):\n            clamp(value, 5, 1)\n\n    def test_range_is_validated_before_the_value_is_compared(self):\n        with pytest.raises(ValueError):\n            clamp(Explosive(), 1, 0)\n\n    @pytest.mark.parametrize((\"low\", \"high\"), [(0, 0), (-3, -3), (2.5, 2.5), (0, 1)])\n    def test_low_not_greater_than_high_does_not_raise(self, low, high):\n        assert clamp(low, low, high) == low\n\n    def test_invalid_string_range_raises(self):\n        with pytest.raises(ValueError, match=\"low must not exceed high\"):\n            clamp(\"m\", \"z\", \"a\")\n\n\nclass TestNonNumericComparableTypes:\n    @pytest.mark.parametrize(\n        (\"value\", \"expected\"),\n        [(\"m\", \"m\"), (\"a\", \"a\"), (\"z\", \"z\"), (\"A\", \"a\"), (\"~\", \"z\")],\n    )\n    def test_strings(self, value, expected):\n        assert clamp(value, \"a\", \"z\") == expected\n\n    @pytest.mark.parametrize(\n        (\"value\", \"expected\"),\n        [\n            ((1, 5), (1, 5)),\n            ((0, 9), (1, 0)),\n            ((3, 0), (2, 0)),\n            ((1, 0), (1, 0)),\n            ((2, 0), (2, 0)),\n        ],\n    )\n    def test_tuples(self, value, expected):\n        assert clamp(value, (1, 0), (2, 0)) == expected\n\n    @pytest.mark.parametrize(\n        (\"value\", \"expected\"),\n        [\n            (date(2020, 6, 1), date(2020, 6, 1)),\n            (date(2019, 12, 31), date(2020, 1, 1)),\n            (date(2021, 1, 1), date(2020, 12, 31)),\n        ],\n    )\n    def test_dates(self, value, expected):\n        assert clamp(value, date(2020, 1, 1), date(2020, 12, 31)) == expected\n\n    @pytest.mark.parametrize(\n        (\"value\", \"expected\"),\n        [\n            (Decimal(\"0.5\"), Decimal(\"0.5\")),\n            (Decimal(\"-1\"), Decimal(\"0\")),\n            (Decimal(\"2.5\"), Decimal(\"1\")),\n        ],\n    )\n    def test_decimals(self, value, expected):\n        assert clamp(value, Decimal(\"0\"), Decimal(\"1\")) == expected\n\n    def test_fraction_inside_integer_bounds(self):\n        assert clamp(Fraction(1, 2), 0, 1) == Fraction(1, 2)\n\n    def test_fraction_above_integer_bounds_returns_the_integer_bound(self):\n        assert clamp(Fraction(7, 2), 0, 1) == 1\n\n\nclass TestMixedNumericTypes:\n    def test_in_range_preserves_the_value_type(self):\n        result = clamp(5, 0.0, 10.0)\n        assert result == 5\n        assert isinstance(result, int)\n\n    def test_clamped_result_takes_the_bound_type(self):\n        result = clamp(50, 0.0, 10.0)\n        assert result == 10.0\n        assert isinstance(result, float)\n\n    def test_true_inside_integer_range_is_returned(self):\n        assert clamp(True, 0, 5) is True\n\n    def test_false_below_integer_range_is_clamped(self):\n        assert clamp(False, 2, 5) == 2\n\n\nclass TestInfinities:\n    def test_positive_infinity_clamps_to_high(self):\n        assert clamp(math.inf, 0, 10) == 10\n\n    def test_negative_infinity_clamps_to_low(self):\n        assert clamp(-math.inf, 0, 10) == 0\n\n    def test_unbounded_range_returns_the_value(self):\n        assert clamp(1234.5, -math.inf, math.inf) == 1234.5\n\n    def test_infinite_bounds_are_a_valid_range(self):\n        assert clamp(math.inf, -math.inf, math.inf) == math.inf\n\n\nclass TestNaNPassesThrough:\n    def test_nan_value_is_returned_unchanged(self):\n        assert math.isnan(clamp(float(\"nan\"), 0, 10))\n\n    def test_nan_low_is_not_treated_as_an_invalid_range(self):\n        assert clamp(50, float(\"nan\"), 10) == 10\n\n    def test_nan_high_is_not_treated_as_an_invalid_range(self):\n        assert clamp(-50, 0, float(\"nan\")) == 0\n\n\nVALUES = [-1000, -7, -1, 0, 0.5, 1, 3, 7.25, 10, 11, 1000]\nRANGES = [(0, 10), (-5, 5), (2, 2), (-100, -50), (0.5, 1.5), (-math.inf, 0)]\n\n\n@pytest.mark.parametrize((\"low\", \"high\"), RANGES)\n@pytest.mark.parametrize(\"value\", VALUES)\nclass TestProperties:\n    def test_result_never_falls_outside_the_range(self, value, low, high):\n        result = clamp(value, low, high)\n        assert low <= result <= high\n\n    def test_result_is_the_value_or_one_of_the_bounds(self, value, low, high):\n        assert clamp(value, low, high) in (value, low, high)\n\n    def test_clamping_is_idempotent(self, value, low, high):\n        once = clamp(value, low, high)\n        assert clamp(once, low, high) == once\n\n    def test_value_already_inside_the_range_is_untouched(self, value, low, high):\n        if low <= value <= high:\n            assert clamp(value, low, high) == value\n\n\nclass TestBehaviouralInvariants:\n    def test_result_is_monotonic_in_the_value(self):\n        values = [-100, -10, -0.5, 0, 0.5, 1, 4, 9.5, 10, 11, 100]\n        results = [clamp(v, 0, 10) for v in values]\n        assert results == sorted(results)\n\n    def test_widening_the_range_never_moves_the_result_further_from_the_value(self):\n        assert clamp(20, 0, 10) == 10\n        assert clamp(20, 0, 15) == 15\n        assert clamp(20, 0, 25) == 20\n\n    def test_inputs_are_not_mutated(self):\n        value, low, high = [1, 2], [0], [9]\n        clamp(value, low, high)\n        assert (value, low, high) == ([1, 2], [0], [9])\n\n    def test_repeated_calls_are_stable(self):\n        assert [clamp(42, 0, 10) for _ in range(5)] == [10] * 5\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-opus-5",
   "subject": "parse_range",
   "status": "ok",
   "tests": 71,
   "mutants": 2,
   "killed": [
    "no_order_check",
    "inclusive_off_by_one"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\n\nfrom subject import parse_range\n\n\nclass TestValidRanges:\n    @pytest.mark.parametrize(\n        \"text, expected\",\n        [\n            (\"1-5\", (1, 5)),\n            (\"0-0\", (0, 0)),\n            (\"3-3\", (3, 3)),\n            (\"0-1\", (0, 1)),\n            (\"10-200\", (10, 200)),\n            (\"007-010\", (7, 10)),\n            (\"+1-+5\", (1, 5)),\n            (\"1_0-2_0\", (10, 20)),\n            (\" 1 - 5 \", (1, 5)),\n            (\"\\t1\\n-\\n5\\t\", (1, 5)),\n            (\"\\u0661-\\u0665\", (1, 5)),\n            (\n                \"123456789012345678901234567890-123456789012345678901234567891\",\n                (123456789012345678901234567890, 123456789012345678901234567891),\n            ),\n        ],\n    )\n    def test_returns_expected_pair(self, text, expected):\n        assert parse_range(text) == expected\n\n    def test_result_is_tuple_of_two_ints(self):\n        result = parse_range(\"2-4\")\n        assert isinstance(result, tuple)\n        assert len(result) == 2\n        assert all(type(value) is int for value in result)\n\n    def test_result_is_unpackable(self):\n        start, end = parse_range(\"12-34\")\n        assert start == 12\n        assert end == 34\n\n    def test_equal_bounds_are_allowed(self):\n        assert parse_range(\"8-8\") == (8, 8)\n\n    def test_repeated_calls_are_stable(self):\n        assert parse_range(\"1-2\") == parse_range(\"1-2\") == (1, 2)\n\n\nclass TestPartCount:\n    @pytest.mark.parametrize(\n        \"text\",\n        [\n            \"\",\n            \" \",\n            \"   \",\n            \"5\",\n            \"abc\",\n            \"12345\",\n        ],\n    )\n    def test_missing_separator_is_rejected(self, text):\n        with pytest.raises(ValueError, match=\"expected two parts\"):\n            parse_range(text)\n\n    @pytest.mark.parametrize(\n        \"text\",\n        [\n            \"1-2-3\",\n            \"1-2-\",\n            \"-1-5\",\n            \"1--5\",\n            \"--\",\n            \"---\",\n            \"-5--1\",\n            \"a-b-c\",\n        ],\n    )\n    def test_extra_separators_are_rejected(self, text):\n        with pytest.raises(ValueError, match=\"expected two parts\"):\n            parse_range(text)\n\n    def test_part_count_is_checked_before_ordering(self):\n        with pytest.raises(ValueError, match=\"expected two parts\"):\n            parse_range(\"9-5-1\")\n\n    def test_part_count_is_checked_before_integer_conversion(self):\n        with pytest.raises(ValueError, match=\"expected two parts\"):\n            parse_range(\"a-b-c\")\n\n\nclass TestNonIntegerParts:\n    @pytest.mark.parametrize(\n        \"text\",\n        [\n            \"-\",\n            \" - \",\n            \"-5\",\n            \"5-\",\n            \"a-b\",\n            \"1-b\",\n            \"a-5\",\n            \"one-two\",\n            \"1.5-2\",\n            \"1-2.5\",\n            \"1e3-2e3\",\n            \"0x1-0xf\",\n            \"1 2-3\",\n            \"١٢٣abc-5\",\n        ],\n    )\n    def test_non_integer_parts_raise_value_error(self, text):\n        with pytest.raises(ValueError, match=\"invalid literal for int\"):\n            parse_range(text)\n\n    @pytest.mark.parametrize(\"text\", [\"-5\", \"5-\", \"-\"])\n    def test_conversion_failure_is_not_reported_as_a_module_error(self, text):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(text)\n        message = str(excinfo.value)\n        assert \"expected two parts\" not in message\n        assert \"start after end\" not in message\n\n    def test_conversion_failure_wins_over_ordering(self):\n        with pytest.raises(ValueError, match=\"invalid literal for int\"):\n            parse_range(\"9-abc\")\n\n\nclass TestOrdering:\n    @pytest.mark.parametrize(\n        \"text\",\n        [\n            \"5-3\",\n            \"4-3\",\n            \"1-0\",\n            \"100-99\",\n            \"10-2\",\n            \" 7 - 6 \",\n            \"0_2-0_1\",\n        ],\n    )\n    def test_start_after_end_is_rejected(self, text):\n        with pytest.raises(ValueError, match=\"start after end\"):\n            parse_range(text)\n\n    def test_boundary_between_valid_and_invalid_ordering(self):\n        assert parse_range(\"3-3\") == (3, 3)\n        with pytest.raises(ValueError, match=\"start after end\"):\n            parse_range(\"4-3\")\n\n    def test_large_reversed_range_is_rejected(self):\n        with pytest.raises(ValueError, match=\"start after end\"):\n            parse_range(\"99999999999999999999-1\")\n\n\nclass TestNonStringInput:\n    @pytest.mark.parametrize(\n        \"value\",\n        [\n            None,\n            15,\n            1.5,\n            True,\n            [\"1\", \"5\"],\n            (\"1\", \"5\"),\n            {\"1\": \"5\"},\n            {1, 5},\n            object(),\n        ],\n    )\n    def test_objects_without_split_raise_attribute_error(self, value):\n        with pytest.raises(AttributeError):\n            parse_range(value)\n\n    def test_bytes_input_raises_type_error(self):\n        with pytest.raises(TypeError):\n            parse_range(b\"1-5\")\n\n    def test_bytearray_input_raises_type_error(self):\n        with pytest.raises(TypeError):\n            parse_range(bytearray(b\"1-5\"))\n\n    def test_missing_argument_raises_type_error(self):\n        with pytest.raises(TypeError):\n            parse_range()\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-opus-5",
   "subject": "chunk",
   "status": "ok",
   "tests": 145,
   "mutants": 2,
   "killed": [
    "drops_remainder",
    "allows_zero_size"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\n\nfrom subject import chunk\n\n\ndef test_splits_evenly_divisible_list():\n    assert chunk([1, 2, 3, 4, 5, 6], 2) == [[1, 2], [3, 4], [5, 6]]\n\n\ndef test_last_chunk_holds_the_remainder():\n    assert chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4], [5]]\n\n\ndef test_size_one_wraps_every_item_individually():\n    assert chunk([1, 2, 3], 1) == [[1], [2], [3]]\n\n\ndef test_size_equal_to_length_returns_one_chunk():\n    assert chunk([1, 2, 3], 3) == [[1, 2, 3]]\n\n\ndef test_size_larger_than_length_returns_one_chunk():\n    assert chunk([1, 2, 3], 100) == [[1, 2, 3]]\n\n\ndef test_single_item_input():\n    assert chunk([7], 4) == [[7]]\n\n\ndef test_empty_input_returns_empty_list():\n    assert chunk([], 3) == []\n\n\ndef test_empty_input_with_size_one_returns_empty_list():\n    assert chunk([], 1) == []\n\n\ndef test_first_element_is_never_dropped():\n    assert chunk([\"a\", \"b\", \"c\"], 2)[0][0] == \"a\"\n\n\ndef test_last_element_is_never_dropped():\n    assert chunk([\"a\", \"b\", \"c\"], 2)[-1][-1] == \"c\"\n\n\ndef test_chunks_do_not_overlap():\n    assert chunk([1, 2, 3, 4], 2) == [[1, 2], [3, 4]]\n\n\ndef test_falsy_items_are_preserved():\n    assert chunk([0, None, False, \"\"], 2) == [[0, None], [False, \"\"]]\n\n\ndef test_nested_items_are_preserved_untouched():\n    inner = [1, 2]\n    result = chunk([inner, \"x\", 3.5], 2)\n    assert result == [[inner, \"x\"], [3.5]]\n    assert result[0][0] is inner\n\n\ndef test_duplicate_items_are_all_kept():\n    assert chunk([1, 1, 1], 2) == [[1, 1], [1]]\n\n\ndef test_returns_a_plain_list_of_plain_lists():\n    result = chunk([1, 2, 3], 2)\n    assert isinstance(result, list)\n    assert all(isinstance(part, list) for part in result)\n\n\ndef test_works_on_strings_and_preserves_the_sequence_type():\n    assert chunk(\"abcdef\", 2) == [\"ab\", \"cd\", \"ef\"]\n\n\ndef test_string_with_remainder():\n    assert chunk(\"abcde\", 2) == [\"ab\", \"cd\", \"e\"]\n\n\ndef test_works_on_tuples_and_preserves_the_sequence_type():\n    assert chunk((1, 2, 3), 2) == [(1, 2), (3,)]\n\n\ndef test_works_on_range_objects():\n    assert chunk(range(5), 2) == [range(0, 2), range(2, 4), range(4, 5)]\n\n\ndef test_works_on_bytes():\n    assert chunk(b\"abcd\", 3) == [b\"abc\", b\"d\"]\n\n\ndef test_input_list_is_not_mutated():\n    data = [1, 2, 3, 4, 5]\n    chunk(data, 2)\n    assert data == [1, 2, 3, 4, 5]\n\n\ndef test_chunks_are_copies_not_views_of_the_input():\n    data = [1, 2, 3, 4]\n    result = chunk(data, 2)\n    result[0][0] = 99\n    assert data == [1, 2, 3, 4]\n\n\ndef test_result_is_a_fresh_object_on_each_call():\n    data = [1, 2, 3, 4]\n    first = chunk(data, 2)\n    second = chunk(data, 2)\n    assert first == second\n    assert first is not second\n    assert first[0] is not second[0]\n\n\n@pytest.mark.parametrize(\"size\", [0, -1, -2, -100])\ndef test_non_positive_size_raises_value_error(size):\n    with pytest.raises(ValueError):\n        chunk([1, 2, 3], size)\n\n\ndef test_value_error_carries_the_expected_message():\n    with pytest.raises(ValueError, match=\"size must be positive\"):\n        chunk([1, 2, 3], 0)\n\n\ndef test_zero_size_raises_even_for_empty_input():\n    with pytest.raises(ValueError):\n        chunk([], 0)\n\n\ndef test_size_is_validated_before_the_items_are_touched():\n    with pytest.raises(ValueError):\n        chunk(None, 0)\n\n\ndef test_size_one_is_accepted_and_does_not_raise():\n    assert chunk([1], 1) == [[1]]\n\n\ndef test_unsized_input_raises_type_error():\n    with pytest.raises(TypeError):\n        chunk(iter([1, 2, 3]), 2)\n\n\ndef test_non_integer_size_raises_type_error():\n    with pytest.raises(TypeError):\n        chunk([1, 2, 3], 2.5)\n\n\n@pytest.mark.parametrize(\"length\", [0, 1, 2, 3, 5, 8, 9, 20])\n@pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 5, 7, 25])\ndef test_chunking_preserves_order_and_contents(length, size):\n    data = list(range(length))\n    result = chunk(data, size)\n    assert [item for part in result for item in part] == data\n\n\n@pytest.mark.parametrize(\"length\", [0, 1, 2, 3, 5, 8, 9, 20])\n@pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 5, 7, 25])\ndef test_chunk_count_and_shapes(length, size):\n    data = list(range(length))\n    result = chunk(data, size)\n    assert len(result) == -(-length // size)\n    assert all(len(part) == size for part in result[:-1])\n    if result:\n        assert 1 <= len(result[-1]) <= size\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-opus-5",
   "subject": "normalise_spaces",
   "status": "ok",
   "tests": 305,
   "mutants": 2,
   "killed": [
    "only_strips_ends",
    "collapses_but_keeps_tabs"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\n\nfrom subject import normalise_spaces\n\n\nASCII_WHITESPACE = [\" \", \"\\t\", \"\\n\", \"\\r\", \"\\v\", \"\\f\"]\n\nUNICODE_WHITESPACE = [\n    \"\\x1c\",\n    \"\\x1d\",\n    \"\\x1e\",\n    \"\\x1f\",\n    \"\\x85\",\n    \"\\xa0\",\n    \"\\u1680\",\n    \"\\u2000\",\n    \"\\u2001\",\n    \"\\u2002\",\n    \"\\u2003\",\n    \"\\u2004\",\n    \"\\u2005\",\n    \"\\u2006\",\n    \"\\u2007\",\n    \"\\u2008\",\n    \"\\u2009\",\n    \"\\u200a\",\n    \"\\u2028\",\n    \"\\u2029\",\n    \"\\u202f\",\n    \"\\u205f\",\n    \"\\u3000\",\n]\n\nNON_WHITESPACE_INVISIBLES = [\"\\u200b\", \"\\u200c\", \"\\u200d\", \"\\u2060\", \"\\ufeff\"]\n\nCORPUS = [\n    \"\",\n    \" \",\n    \"      \",\n    \"\\t\\n\\r\\v\\f\",\n    \"word\",\n    \" word \",\n    \"two words\",\n    \"two    words\",\n    \"   leading and trailing   \",\n    \"a\\tb\\nc\\rd\",\n    \"line one\\nline two\\n\\n\\nline three\",\n    \"\\xa0nbsp\\xa0around\\xa0\",\n    \"punctuation , and ; stay  put\",\n    \"emoji \\U0001f642   and   \\U0001f389\",\n    \"trailing newline\\n\",\n    \"\\n\\n\\n\",\n    \"x\" * 200,\n    \" \".join([\"tok\"] * 50),\n    \"   \".join([\"tok\"] * 50),\n    \"mixed \\t \\n \\r \\v \\f separators\",\n    \"\\u200bzero\\u200bwidth\\u200b\",\n    \"tabs\\t\\t\\tonly\",\n    \"a\" + \" \" * 500 + \"b\",\n]\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\"\", \"\"),\n        (\" \", \"\"),\n        (\"      \", \"\"),\n        (\"\\t\", \"\"),\n        (\"\\n\", \"\"),\n        (\"\\r\\n\", \"\"),\n        (\"\\v\\f\", \"\"),\n        (\" \\t\\n\\r\\v\\f \", \"\"),\n        (\"\\xa0\", \"\"),\n        (\"\\u3000\\u2003\", \"\"),\n    ],\n)\ndef test_empty_or_whitespace_only_input_returns_empty_string(text, expected):\n    assert normalise_spaces(text) == expected\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"word\",\n        \"two words\",\n        \"a b c d e\",\n        \"hello, world!\",\n        \"1 2 3\",\n        \"Ünïcödé wörds stay\",\n        \"a\",\n    ],\n)\ndef test_already_normalised_text_is_returned_unchanged(text):\n    assert normalise_spaces(text) == text\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\"two  words\", \"two words\"),\n        (\"two          words\", \"two words\"),\n        (\"a  b  c\", \"a b c\"),\n        (\"a \\t b\", \"a b\"),\n        (\"a\\t\\tb\", \"a b\"),\n        (\"a\\n\\nb\", \"a b\"),\n        (\"a\\r\\r\\rb\", \"a b\"),\n        (\"a \\t\\n\\r\\v\\f b\", \"a b\"),\n        (\"one\\ttwo\\nthree\\rfour\", \"one two three four\"),\n        (\"line one\\n\\n\\nline two\", \"line one line two\"),\n        (\"windows\\r\\nnewline\", \"windows newline\"),\n    ],\n)\ndef test_internal_whitespace_runs_collapse_to_single_space(text, expected):\n    assert normalise_spaces(text) == expected\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\" word\", \"word\"),\n        (\"word \", \"word\"),\n        (\"    word    \", \"word\"),\n        (\"\\n\\tword\\t\\n\", \"word\"),\n        (\"\\r\\n  a b  \\r\\n\", \"a b\"),\n        (\"  a  b  \", \"a b\"),\n        (\"\\xa0padded\\xa0\", \"padded\"),\n        (\"trailing newline\\n\", \"trailing newline\"),\n    ],\n)\ndef test_leading_and_trailing_whitespace_is_stripped(text, expected):\n    assert normalise_spaces(text) == expected\n\n\n@pytest.mark.parametrize(\"ws\", ASCII_WHITESPACE)\ndef test_every_ascii_whitespace_character_acts_as_a_separator(ws):\n    assert normalise_spaces(\"left\" + ws + \"right\") == \"left right\"\n    assert normalise_spaces(ws * 3 + \"solo\" + ws * 3) == \"solo\"\n    assert normalise_spaces(ws) == \"\"\n\n\n@pytest.mark.parametrize(\"ws\", UNICODE_WHITESPACE)\ndef test_unicode_whitespace_characters_act_as_separators(ws):\n    result = normalise_spaces(\"left\" + ws + \"right\")\n    assert result == \"left right\"\n    assert ws not in result\n    assert normalise_spaces(ws * 4) == \"\"\n\n\n@pytest.mark.parametrize(\"ws\", ASCII_WHITESPACE + UNICODE_WHITESPACE)\ndef test_mixed_whitespace_run_collapses_to_exactly_one_space(ws):\n    text = \"a\" + ws + \" \\t\" + ws + \"\\n\" + \"b\"\n    assert normalise_spaces(text) == \"a b\"\n\n\n@pytest.mark.parametrize(\"char\", NON_WHITESPACE_INVISIBLES)\ndef test_zero_width_and_formatting_characters_are_preserved(char):\n    assert normalise_spaces(\"a\" + char + \"b\") == \"a\" + char + \"b\"\n    assert normalise_spaces(\"  a \" + char + \" b  \") == \"a \" + char + \" b\"\n    assert normalise_spaces(char) == char\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\"keep   ,   punctuation\", \"keep , punctuation\"),\n        (\"path/to  /file\", \"path/to /file\"),\n        (\"a--b  c\", \"a--b c\"),\n        (\"  \\U0001f642   \\U0001f389  \", \"\\U0001f642 \\U0001f389\"),\n        (\"naïve  café\", \"naïve café\"),\n        (\"<tag>  </tag>\", \"<tag> </tag>\"),\n    ],\n)\ndef test_non_whitespace_characters_are_untouched(text, expected):\n    assert normalise_spaces(text) == expected\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_result_is_always_a_string(text):\n    assert isinstance(normalise_spaces(text), str)\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_result_has_no_leading_or_trailing_whitespace(text):\n    result = normalise_spaces(text)\n    assert result == result.strip()\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_result_never_contains_a_double_space(text):\n    assert \"  \" not in normalise_spaces(text)\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_space_is_the_only_whitespace_left_in_the_result(text):\n    result = normalise_spaces(text)\n    offenders = [ch for ch in result if ch.isspace() and ch != \" \"]\n    assert offenders == []\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_tokens_and_their_order_are_preserved(text):\n    result = normalise_spaces(text)\n    tokens = text.split()\n    assert result.split(\" \") == (tokens if tokens else [\"\"])\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_is_idempotent(text):\n    once = normalise_spaces(text)\n    assert normalise_spaces(once) == once\n    assert normalise_spaces(normalise_spaces(once)) == once\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_is_deterministic_and_does_not_alter_its_input(text):\n    original = text\n    first = normalise_spaces(text)\n    second = normalise_spaces(text)\n    assert first == second\n    assert text == original\n\n\n@pytest.mark.parametrize(\"text\", CORPUS)\ndef test_result_is_never_longer_than_the_input(text):\n    assert len(normalise_spaces(text)) <= len(text)\n\n\ndef test_word_count_is_preserved_across_a_large_input():\n    text = (\"  word  \" * 10_000) + \"\\n\\t\"\n    result = normalise_spaces(text)\n    assert result.split(\" \") == [\"word\"] * 10_000\n    assert not result.startswith(\" \")\n    assert not result.endswith(\" \")\n\n\ndef test_multiline_paragraph_becomes_a_single_line():\n    text = \"\"\"\n        The quick   brown fox\n        jumps over\n\n        the lazy dog.\n    \"\"\"\n    assert normalise_spaces(text) == \"The quick brown fox jumps over the lazy dog.\"\n\n\ndef test_single_token_surrounded_by_every_whitespace_kind():\n    padding = \"\".join(ASCII_WHITESPACE + UNICODE_WHITESPACE)\n    assert normalise_spaces(padding + \"solo\" + padding) == \"solo\"\n\n\n@pytest.mark.parametrize(\"bad\", [None, 0, 1.5, True, [], [\"a\", \"b\"], {}, {\"a\": 1}, object()])\ndef test_non_string_input_raises_attribute_error(bad):\n    with pytest.raises(AttributeError):\n        normalise_spaces(bad)\n\n\n@pytest.mark.parametrize(\"bad\", [b\"a  b\", bytearray(b\"a  b\")])\ndef test_bytes_input_raises_type_error(bad):\n    with pytest.raises(TypeError):\n        normalise_spaces(bad)\n\n\ndef test_missing_argument_raises_type_error():\n    with pytest.raises(TypeError):\n        normalise_spaces()\n\n\ndef test_str_subclass_input_returns_a_plain_normalised_string():\n    class MyStr(str):\n        pass\n\n    result = normalise_spaces(MyStr(\"  a   b  \"))\n    assert result == \"a b\"\n    assert isinstance(result, str)\n",
   "detail": null
  },
  {
   "round": "r1",
   "model": "claude-opus-5",
   "subject": "percent_change",
   "status": "ok",
   "tests": 128,
   "mutants": 2,
   "killed": [
    "wrong_denominator",
    "forgets_percent"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import math\nfrom decimal import Decimal\nfrom fractions import Fraction\n\nimport pytest\n\nfrom subject import percent_change\n\n\nclass TestBasicResults:\n    @pytest.mark.parametrize(\n        (\"old\", \"new\", \"expected\"),\n        [\n            (100, 150, 50.0),\n            (100, 50, -50.0),\n            (100, 100, 0.0),\n            (100, 0, -100.0),\n            (100, 200, 100.0),\n            (100, 300, 200.0),\n            (50, 200, 300.0),\n            (200, 50, -75.0),\n            (1, 2, 100.0),\n            (2, 1, -50.0),\n            (3, 1, -200.0 / 3.0),\n            (1_000_000, 1_500_000, 50.0),\n            (0.1, 0.2, 100.0),\n            (2.5, 3.0, 20.0),\n            (1.5, 1.0, -100.0 / 3.0),\n        ],\n    )\n    def test_returns_expected_percentage(self, old, new, expected):\n        assert percent_change(old, new) == pytest.approx(expected)\n\n    @pytest.mark.parametrize(\n        (\"old\", \"new\", \"expected\"),\n        [\n            (100, 150, 50.0),\n            (100, 50, -50.0),\n            (100, 100, 0.0),\n            (100, 0, -100.0),\n            (50, 200, 300.0),\n            (200, 50, -75.0),\n        ],\n    )\n    def test_exact_for_representable_cases(self, old, new, expected):\n        assert percent_change(old, new) == expected\n\n    def test_identical_values_give_exactly_zero(self):\n        assert percent_change(7.5, 7.5) == 0.0\n\n    def test_new_of_zero_is_minus_one_hundred_percent(self):\n        assert percent_change(42, 0) == -100.0\n\n    def test_result_is_float_for_int_inputs(self):\n        assert type(percent_change(100, 150)) is float\n\n    def test_large_integers_do_not_overflow(self):\n        assert percent_change(10**400, 3 * 10**400) == pytest.approx(200.0)\n\n    def test_very_small_magnitudes(self):\n        assert percent_change(1e-300, 2e-300) == pytest.approx(100.0)\n\n\nclass TestNegativeAndMixedSigns:\n    @pytest.mark.parametrize(\n        (\"old\", \"new\", \"expected\"),\n        [\n            (-100, -50, -50.0),\n            (-100, -150, 50.0),\n            (-100, -100, 0.0),\n            (-100, 0, -100.0),\n            (-50, 50, -200.0),\n            (50, -50, -200.0),\n            (-4, -1, -75.0),\n            (-2.5, -5.0, 100.0),\n        ],\n    )\n    def test_negative_old_inverts_the_sign(self, old, new, expected):\n        assert percent_change(old, new) == pytest.approx(expected)\n\n    def test_growth_from_positive_old_is_positive(self):\n        assert percent_change(10, 11) > 0\n\n    def test_growth_from_negative_old_is_negative(self):\n        assert percent_change(-10, -5) < 0\n\n    def test_shrink_from_positive_old_is_negative(self):\n        assert percent_change(11, 10) < 0\n\n    def test_shrink_from_negative_old_is_positive(self):\n        assert percent_change(-5, -10) > 0\n\n\nclass TestZeroOldRaises:\n    @pytest.mark.parametrize(\n        \"old\",\n        [0, 0.0, -0.0, False, Decimal(0), Decimal(\"0.000\"), Fraction(0, 1)],\n        ids=[\n            \"int-zero\",\n            \"float-zero\",\n            \"negative-float-zero\",\n            \"bool-false\",\n            \"decimal-zero\",\n            \"decimal-scaled-zero\",\n            \"fraction-zero\",\n        ],\n    )\n    def test_any_zero_old_raises(self, old):\n        with pytest.raises(ZeroDivisionError):\n            percent_change(old, 10)\n\n    @pytest.mark.parametrize(\"new\", [0, 1, -1, 1e9, 0.5, float(\"nan\"), float(\"inf\")])\n    def test_raises_regardless_of_new(self, new):\n        with pytest.raises(ZeroDivisionError):\n            percent_change(0, new)\n\n    def test_error_message_is_exact(self):\n        with pytest.raises(ZeroDivisionError) as excinfo:\n            percent_change(0, 10)\n        assert str(excinfo.value) == \"old must not be zero\"\n\n    def test_error_type_is_exactly_zero_division_error(self):\n        with pytest.raises(ZeroDivisionError) as excinfo:\n            percent_change(0, 10)\n        assert type(excinfo.value) is ZeroDivisionError\n\n    def test_guard_runs_before_new_is_used(self):\n        with pytest.raises(ZeroDivisionError):\n            percent_change(0, \"not a number\")\n\n    def test_matches_message_pattern(self):\n        with pytest.raises(ZeroDivisionError, match=\"old must not be zero\"):\n            percent_change(0.0, 5)\n\n\nclass TestNumericTypes:\n    def test_bools_behave_as_ints(self):\n        assert percent_change(True, False) == -100.0\n\n    def test_decimal_inputs_stay_decimal(self):\n        result = percent_change(Decimal(\"100\"), Decimal(\"150\"))\n        assert isinstance(result, Decimal)\n        assert result == Decimal(\"50\")\n\n    def test_decimal_negative_change(self):\n        assert percent_change(Decimal(\"200\"), Decimal(\"50\")) == Decimal(\"-75\")\n\n    def test_fraction_inputs_are_exact(self):\n        result = percent_change(Fraction(3), Fraction(1))\n        assert isinstance(result, Fraction)\n        assert result == Fraction(-200, 3)\n\n    def test_mixed_int_and_decimal_is_supported(self):\n        assert percent_change(Decimal(\"100\"), 150) == Decimal(\"50\")\n\n    def test_mixed_float_and_decimal_raises_type_error(self):\n        with pytest.raises(TypeError):\n            percent_change(Decimal(\"100\"), 1.5)\n\n\nclass TestSpecialFloats:\n    def test_infinite_new_gives_positive_infinity(self):\n        assert percent_change(1.0, float(\"inf\")) == math.inf\n\n    def test_negative_infinite_new_gives_negative_infinity(self):\n        assert percent_change(1.0, float(\"-inf\")) == -math.inf\n\n    def test_infinite_old_gives_nan(self):\n        assert math.isnan(percent_change(float(\"inf\"), 1.0))\n\n    def test_nan_new_propagates(self):\n        assert math.isnan(percent_change(1.0, float(\"nan\")))\n\n    def test_nan_old_propagates(self):\n        assert math.isnan(percent_change(float(\"nan\"), 1.0))\n\n\nclass TestNonNumericInputs:\n    @pytest.mark.parametrize(\n        (\"old\", \"new\"),\n        [\n            (\"100\", \"150\"),\n            (\"0\", \"5\"),\n            (None, 1),\n            (1, None),\n            ([1], [2]),\n            (1, \"2\"),\n            (\"1\", 2),\n            ({}, 1),\n        ],\n    )\n    def test_raises_type_error(self, old, new):\n        with pytest.raises(TypeError):\n            percent_change(old, new)\n\n\nclass TestCallingConvention:\n    def test_accepts_keyword_arguments(self):\n        assert percent_change(old=100, new=150) == pytest.approx(50.0)\n\n    def test_accepts_mixed_positional_and_keyword(self):\n        assert percent_change(100, new=150) == pytest.approx(50.0)\n\n    def test_argument_order_matters(self):\n        assert percent_change(100, 150) != percent_change(150, 100)\n\n    @pytest.mark.parametrize(\"args\", [(), (1,), (1, 2, 3)])\n    def test_wrong_arity_raises_type_error(self, args):\n        with pytest.raises(TypeError):\n            percent_change(*args)\n\n    def test_unknown_keyword_raises_type_error(self):\n        with pytest.raises(TypeError):\n            percent_change(100, 150, base=1)\n\n    def test_duplicate_argument_raises_type_error(self):\n        with pytest.raises(TypeError):\n            percent_change(100, 150, old=100)\n\n\nclass TestProperties:\n    @pytest.mark.parametrize(\"old\", [1, 2.5, 100, 1e6, -7])\n    @pytest.mark.parametrize(\"delta\", [0.5, 1, 10, -0.5, -3])\n    def test_round_trip_reconstructs_new(self, old, delta):\n        new = old + delta\n        pct = percent_change(old, new)\n        assert old * (1 + pct / 100) == pytest.approx(new)\n\n    @pytest.mark.parametrize(\"k\", [2, -3, 0.5, 1000, -0.25])\n    def test_scaling_both_values_leaves_result_unchanged(self, k):\n        assert percent_change(k * 3, k * 7) == pytest.approx(percent_change(3, 7))\n\n    @pytest.mark.parametrize(\"old\", [1, 4, 12.5, 250])\n    def test_doubling_is_one_hundred_percent(self, old):\n        assert percent_change(old, old * 2) == pytest.approx(100.0)\n\n    @pytest.mark.parametrize(\"old\", [1, 4, 12.5, 250])\n    def test_halving_is_minus_fifty_percent(self, old):\n        assert percent_change(old, old / 2) == pytest.approx(-50.0)\n\n    @pytest.mark.parametrize(\"old\", [1, 4, 12.5, 250, -8])\n    def test_no_change_is_zero_percent(self, old):\n        assert percent_change(old, old) == 0.0\n\n    def test_is_not_antisymmetric(self):\n        assert percent_change(100, 200) != -percent_change(200, 100)\n\n    def test_is_deterministic(self):\n        assert percent_change(37, 91) == percent_change(37, 91)\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-haiku-4-5-20251001",
   "subject": "clamp",
   "status": "ok",
   "tests": 13,
   "mutants": 3,
   "killed": [
    "drops_validation",
    "swapped_bounds"
   ],
   "survived": [
    "boundary_off_by_one"
   ],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\nfrom subject import clamp\n\n\ndef test_clamp_value_within_range():\n    assert clamp(5, 0, 10) == 5\n\n\ndef test_clamp_value_at_low_boundary():\n    assert clamp(0, 0, 10) == 0\n\n\ndef test_clamp_value_at_high_boundary():\n    assert clamp(10, 0, 10) == 10\n\n\ndef test_clamp_value_below_low():\n    assert clamp(-5, 0, 10) == 0\n\n\ndef test_clamp_value_above_high():\n    assert clamp(15, 0, 10) == 10\n\n\ndef test_clamp_low_exceeds_high_raises_error():\n    with pytest.raises(ValueError, match=\"low must not exceed high\"):\n        clamp(5, 10, 0)\n\n\ndef test_clamp_with_negative_range():\n    assert clamp(-5, -10, -1) == -5\n\n\ndef test_clamp_with_negative_value_below_range():\n    assert clamp(-15, -10, 0) == -10\n\n\ndef test_clamp_with_negative_value_above_range():\n    assert clamp(5, -10, 0) == 0\n\n\ndef test_clamp_with_floats():\n    assert clamp(5.5, 0.0, 10.0) == 5.5\n\n\ndef test_clamp_with_float_value_below_range():\n    assert clamp(-5.5, 0.0, 10.0) == 0.0\n\n\ndef test_clamp_with_float_value_above_range():\n    assert clamp(15.5, 0.0, 10.0) == 10.0\n\n\ndef test_clamp_when_low_equals_high():\n    assert clamp(5, 3, 3) == 3\n    assert clamp(3, 3, 3) == 3\n    assert clamp(2, 3, 3) == 3\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-haiku-4-5-20251001",
   "subject": "parse_range",
   "status": "fails_original",
   "tests": null,
   "mutants": null,
   "killed": null,
   "survived": null,
   "kill_count": null,
   "tests_passed": 12,
   "tests_failed": 2,
   "test_src": "import pytest\nfrom subject import parse_range\n\n\ndef test_parse_range_basic():\n    assert parse_range(\"1-5\") == (1, 5)\n\n\ndef test_parse_range_equal():\n    assert parse_range(\"5-5\") == (5, 5)\n\n\ndef test_parse_range_large():\n    assert parse_range(\"100-999\") == (100, 999)\n\n\ndef test_parse_range_zero_start():\n    assert parse_range(\"0-10\") == (0, 10)\n\n\ndef test_parse_range_negative_start():\n    assert parse_range(\"-5-0\") == (-5, 0)\n\n\ndef test_parse_range_both_negative():\n    assert parse_range(\"-10--5\") == (-10, -5)\n\n\ndef test_parse_range_missing_separator():\n    with pytest.raises(ValueError, match=\"expected two parts\"):\n        parse_range(\"15\")\n\n\ndef test_parse_range_extra_separator():\n    with pytest.raises(ValueError, match=\"expected two parts\"):\n        parse_range(\"1-2-3\")\n\n\ndef test_parse_range_non_int_start():\n    with pytest.raises(ValueError):\n        parse_range(\"a-5\")\n\n\ndef test_parse_range_non_int_end():\n    with pytest.raises(ValueError):\n        parse_range(\"1-b\")\n\n\ndef test_parse_range_reversed():\n    with pytest.raises(ValueError, match=\"start after end\"):\n        parse_range(\"10-5\")\n\n\ndef test_parse_range_empty():\n    with pytest.raises(ValueError):\n        parse_range(\"\")\n\n\ndef test_parse_range_only_dash():\n    with pytest.raises(ValueError):\n        parse_range(\"-\")\n\n\ndef test_parse_range_float():\n    with pytest.raises(ValueError):\n        parse_range(\"1.5-5.5\")\n",
   "detail": ") != 2:\n>           raise ValueError(\"expected two parts\")\nE           ValueError: expected two parts\n\nsubject.py:5: ValueError\n=========================== short test summary info ============================\nFAILED test_subject.py::test_parse_range_negative_start - ValueError: expecte...\nFAILED test_subject.py::test_parse_range_both_negative - ValueError: expected...\n2 failed, 12 passed in 0.05s\n"
  },
  {
   "round": "r2",
   "model": "claude-haiku-4-5-20251001",
   "subject": "chunk",
   "status": "ok",
   "tests": 11,
   "mutants": 2,
   "killed": [
    "drops_remainder",
    "allows_zero_size"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\nfrom subject import chunk\n\n\ndef test_chunk_basic():\n    \"\"\"Test basic chunking with even division\"\"\"\n    assert chunk([1, 2, 3, 4, 5, 6], 2) == [[1, 2], [3, 4], [5, 6]]\n\n\ndef test_chunk_with_remainder():\n    \"\"\"Test chunking when items don't divide evenly\"\"\"\n    assert chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4], [5]]\n\n\ndef test_chunk_empty_list():\n    \"\"\"Test chunking an empty list\"\"\"\n    assert chunk([], 2) == []\n\n\ndef test_chunk_size_one():\n    \"\"\"Test with chunk size of 1\"\"\"\n    assert chunk([1, 2, 3], 1) == [[1], [2], [3]]\n\n\ndef test_chunk_size_larger_than_items():\n    \"\"\"Test when chunk size is larger than number of items\"\"\"\n    assert chunk([1, 2, 3], 10) == [[1, 2, 3]]\n\n\ndef test_chunk_size_equals_items_length():\n    \"\"\"Test when chunk size equals the length of items\"\"\"\n    assert chunk([1, 2, 3], 3) == [[1, 2, 3]]\n\n\ndef test_chunk_single_item():\n    \"\"\"Test with a single item\"\"\"\n    assert chunk([42], 2) == [[42]]\n\n\ndef test_chunk_zero_size_raises_error():\n    \"\"\"Test that size of 0 raises ValueError\"\"\"\n    with pytest.raises(ValueError, match=\"size must be positive\"):\n        chunk([1, 2, 3], 0)\n\n\ndef test_chunk_negative_size_raises_error():\n    \"\"\"Test that negative size raises ValueError\"\"\"\n    with pytest.raises(ValueError, match=\"size must be positive\"):\n        chunk([1, 2, 3], -5)\n\n\ndef test_chunk_strings():\n    \"\"\"Test chunking works with strings\"\"\"\n    assert chunk(['a', 'b', 'c', 'd'], 2) == [['a', 'b'], ['c', 'd']]\n\n\ndef test_chunk_large_list():\n    \"\"\"Test chunking a larger list\"\"\"\n    result = chunk(list(range(10)), 3)\n    assert result == [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-haiku-4-5-20251001",
   "subject": "normalise_spaces",
   "status": "ok",
   "tests": 8,
   "mutants": 2,
   "killed": [
    "only_strips_ends",
    "collapses_but_keeps_tabs"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\nfrom subject import normalise_spaces\n\n\ndef test_multiple_spaces():\n    \"\"\"Test collapsing multiple spaces into single spaces.\"\"\"\n    assert normalise_spaces(\"hello  world\") == \"hello world\"\n    assert normalise_spaces(\"hello   world\") == \"hello world\"\n\n\ndef test_leading_trailing_spaces():\n    \"\"\"Test removal of leading and trailing spaces.\"\"\"\n    assert normalise_spaces(\"  hello world  \") == \"hello world\"\n    assert normalise_spaces(\"   hello   \") == \"hello\"\n\n\ndef test_single_space():\n    \"\"\"Test that single spaces are preserved.\"\"\"\n    assert normalise_spaces(\"hello world\") == \"hello world\"\n\n\ndef test_empty_string():\n    \"\"\"Test empty string.\"\"\"\n    assert normalise_spaces(\"\") == \"\"\n\n\ndef test_only_spaces():\n    \"\"\"Test string with only spaces.\"\"\"\n    assert normalise_spaces(\"   \") == \"\"\n\n\ndef test_mixed_whitespace():\n    \"\"\"Test mixed whitespace characters (tabs, newlines, etc).\"\"\"\n    assert normalise_spaces(\"hello\\tworld\") == \"hello world\"\n    assert normalise_spaces(\"hello\\nworld\") == \"hello world\"\n    assert normalise_spaces(\"hello \\t\\n world\") == \"hello world\"\n\n\ndef test_single_word():\n    \"\"\"Test single word with no spaces.\"\"\"\n    assert normalise_spaces(\"hello\") == \"hello\"\n\n\ndef test_multiple_words():\n    \"\"\"Test multiple words with various spacing.\"\"\"\n    assert normalise_spaces(\"one  two   three    four\") == \"one two three four\"\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-haiku-4-5-20251001",
   "subject": "percent_change",
   "status": "fails_original",
   "tests": null,
   "mutants": null,
   "killed": null,
   "survived": null,
   "kill_count": null,
   "tests_passed": 9,
   "tests_failed": 2,
   "test_src": "import pytest\nfrom subject import percent_change\n\n\ndef test_positive_increase():\n    assert percent_change(100, 150) == 50.0\n\n\ndef test_positive_decrease():\n    assert percent_change(100, 50) == -50.0\n\n\ndef test_no_change():\n    assert percent_change(100, 100) == 0.0\n\n\ndef test_double_value():\n    assert percent_change(50, 100) == 100.0\n\n\ndef test_negative_old_to_negative_increase():\n    assert percent_change(-100, -50) == 50.0\n\n\ndef test_negative_to_positive():\n    assert percent_change(-100, 100) == 200.0\n\n\ndef test_positive_to_negative():\n    assert percent_change(100, -100) == -200.0\n\n\ndef test_small_positive_numbers():\n    assert percent_change(0.01, 0.02) == 100.0\n\n\ndef test_floating_point_values():\n    result = percent_change(2.5, 3.75)\n    assert result == 50.0\n\n\ndef test_old_zero_raises_zero_division_error():\n    with pytest.raises(ZeroDivisionError):\n        percent_change(0, 100)\n\n\ndef test_old_zero_with_new_zero_raises_error():\n    with pytest.raises(ZeroDivisionError):\n        percent_change(0, 0)\n",
   "detail": " 100) == 200.0\nE       assert -200.0 == 200.0\nE        +  where -200.0 = percent_change(-100, 100)\n\ntest_subject.py:26: AssertionError\n=========================== short test summary info ============================\nFAILED test_subject.py::test_negative_old_to_negative_increase - assert -50.0...\nFAILED test_subject.py::test_negative_to_positive - assert -200.0 == 200.0\n2 failed, 9 passed in 0.01s\n"
  },
  {
   "round": "r2",
   "model": "claude-opus-5",
   "subject": "clamp",
   "status": "ok",
   "tests": 875,
   "mutants": 3,
   "killed": [
    "boundary_off_by_one",
    "drops_validation",
    "swapped_bounds"
   ],
   "survived": [],
   "kill_count": 3,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import itertools\nimport math\nfrom datetime import date\nfrom decimal import Decimal\nfrom fractions import Fraction\n\nimport pytest\n\nfrom subject import clamp\n\n\nclass OnlyOrdered:\n    def __init__(self, n):\n        self.n = n\n\n    def __lt__(self, other):\n        return self.n < other.n\n\n    def __gt__(self, other):\n        return self.n > other.n\n\n    def __repr__(self):\n        return \"OnlyOrdered({!r})\".format(self.n)\n\n\nINT_GRID = [\n    (value, low, high)\n    for value, low, high in itertools.product(range(-3, 4), repeat=3)\n    if low <= high\n]\n\n\nclass TestValueInsideRange:\n    @pytest.mark.parametrize(\n        \"value, low, high\",\n        [\n            (5, 1, 10),\n            (0, -10, 10),\n            (-5, -10, -1),\n            (2.5, 2.0, 3.0),\n            (0, 0, 0),\n            (100, -100, 1000),\n        ],\n    )\n    def test_returns_value_unchanged(self, value, low, high):\n        assert clamp(value, low, high) == value\n\n    @pytest.mark.parametrize(\n        \"value, low, high\",\n        [\n            (1, 1, 10),\n            (10, 1, 10),\n            (-10, -10, -1),\n            (-1, -10, -1),\n            (0.0, 0.0, 1.0),\n            (1.0, 0.0, 1.0),\n        ],\n    )\n    def test_endpoints_are_inclusive(self, value, low, high):\n        assert clamp(value, low, high) == value\n\n    def test_in_range_value_is_returned_by_identity(self):\n        value = [5]\n        assert clamp(value, [1], [10]) is value\n\n\nclass TestValueBelowRange:\n    @pytest.mark.parametrize(\n        \"value, low, high, expected\",\n        [\n            (0, 1, 10, 1),\n            (-100, -10, 10, -10),\n            (-11, -10, -1, -10),\n            (1.9999, 2.0, 3.0, 2.0),\n            (-1, 0, 0, 0),\n        ],\n    )\n    def test_returns_low(self, value, low, high, expected):\n        assert clamp(value, low, high) == expected\n\n    def test_returned_low_is_the_low_object(self):\n        low = [1]\n        assert clamp([0], low, [10]) is low\n\n\nclass TestValueAboveRange:\n    @pytest.mark.parametrize(\n        \"value, low, high, expected\",\n        [\n            (11, 1, 10, 10),\n            (100, -10, 10, 10),\n            (0, -10, -1, -1),\n            (3.0001, 2.0, 3.0, 3.0),\n            (1, 0, 0, 0),\n        ],\n    )\n    def test_returns_high(self, value, low, high, expected):\n        assert clamp(value, low, high) == expected\n\n    def test_returned_high_is_the_high_object(self):\n        high = [10]\n        assert clamp([11], [1], high) is high\n\n\nclass TestDegenerateRange:\n    def test_equal_bounds_always_collapse_to_that_bound(self):\n        assert clamp(5, 3, 3) == 3\n        assert clamp(1, 3, 3) == 3\n        assert clamp(3, 3, 3) == 3\n\n    def test_equal_bounds_do_not_raise(self):\n        assert clamp(0, 0, 0) == 0\n\n    def test_equal_bounds_above_returns_high_object(self):\n        low, high = [3], [3]\n        assert clamp([5], low, high) is high\n\n    def test_equal_bounds_below_returns_low_object(self):\n        low, high = [3], [3]\n        assert clamp([1], low, high) is low\n\n\nclass TestInvalidRange:\n    @pytest.mark.parametrize(\n        \"value, low, high\",\n        [\n            (5, 10, 1),\n            (0, 1, 0),\n            (0, 0.1, -0.1),\n            (0, 1, 0.999999),\n            (0, math.inf, -math.inf),\n            (\"m\", \"z\", \"a\"),\n            (date(2020, 1, 1), date(2021, 1, 1), date(2020, 1, 1)),\n        ],\n    )\n    def test_raises_value_error_when_low_exceeds_high(self, value, low, high):\n        with pytest.raises(ValueError):\n            clamp(value, low, high)\n\n    def test_error_message_is_exact(self):\n        with pytest.raises(ValueError) as excinfo:\n            clamp(5, 10, 1)\n        assert str(excinfo.value) == \"low must not exceed high\"\n\n    def test_error_message_matches_pattern(self):\n        with pytest.raises(ValueError, match=r\"^low must not exceed high$\"):\n            clamp(5, 10, 1)\n\n    def test_raises_plain_value_error_not_a_subclass(self):\n        with pytest.raises(ValueError) as excinfo:\n            clamp(5, 10, 1)\n        assert type(excinfo.value) is ValueError\n\n    @pytest.mark.parametrize(\"value\", [None, object(), \"not comparable to ints\"])\n    def test_range_is_validated_before_the_value_is_inspected(self, value):\n        with pytest.raises(ValueError):\n            clamp(value, 10, 1)\n\n\nclass TestIncomparableOperands:\n    @pytest.mark.parametrize(\n        \"value, low, high\",\n        [\n            (None, 1, 10),\n            (\"5\", 1, 10),\n            (1, \"a\", \"z\"),\n            ([1], 1, 10),\n            (object(), 1, 10),\n        ],\n    )\n    def test_raises_type_error(self, value, low, high):\n        with pytest.raises(TypeError):\n            clamp(value, low, high)\n\n\nclass TestFloatSpecialValues:\n    def test_infinite_bounds_pass_every_finite_value_through(self):\n        assert clamp(12345.678, -math.inf, math.inf) == 12345.678\n\n    def test_positive_infinity_is_clamped_to_high(self):\n        assert clamp(math.inf, 0, 10) == 10\n\n    def test_negative_infinity_is_clamped_to_low(self):\n        assert clamp(-math.inf, 0, 10) == 0\n\n    def test_infinite_value_within_infinite_bounds_is_returned(self):\n        assert clamp(math.inf, -math.inf, math.inf) == math.inf\n\n    def test_nan_value_is_returned_unchanged(self):\n        assert math.isnan(clamp(math.nan, 0, 10))\n\n    @pytest.mark.parametrize(\n        \"low, high\",\n        [\n            (math.nan, 10),\n            (0, math.nan),\n            (math.nan, math.nan),\n        ],\n    )\n    def test_nan_bounds_are_silently_ignored(self, low, high):\n        assert clamp(5, low, high) == 5\n\n    def test_negative_zero_sign_is_preserved(self):\n        result = clamp(-0.0, 0.0, 1.0)\n        assert result == 0.0\n        assert math.copysign(1.0, result) == -1.0\n\n    def test_mixed_int_and_float_operands(self):\n        assert clamp(5, 0.0, 2.5) == 2.5\n        assert clamp(-5, 0.0, 2.5) == 0.0\n        assert clamp(1, 0.0, 2.5) == 1\n\n\nclass TestOtherOrderedTypes:\n    @pytest.mark.parametrize(\n        \"value, expected\",\n        [\n            (\"m\", \"m\"),\n            (\"a\", \"a\"),\n            (\"z\", \"z\"),\n            (\"A\", \"a\"),\n            (\"~\", \"z\"),\n        ],\n    )\n    def test_strings(self, value, expected):\n        assert clamp(value, \"a\", \"z\") == expected\n\n    def test_tuples(self):\n        assert clamp((1, 5), (1, 0), (2, 0)) == (1, 5)\n        assert clamp((0, 9), (1, 0), (2, 0)) == (1, 0)\n        assert clamp((3, 0), (1, 0), (2, 0)) == (2, 0)\n\n    def test_dates(self):\n        low, high = date(2020, 1, 1), date(2021, 1, 1)\n        assert clamp(date(2020, 6, 1), low, high) == date(2020, 6, 1)\n        assert clamp(date(2019, 6, 1), low, high) == low\n        assert clamp(date(2022, 6, 1), low, high) == high\n\n    def test_fractions(self):\n        assert clamp(Fraction(1, 2), Fraction(0), Fraction(1)) == Fraction(1, 2)\n        assert clamp(Fraction(3, 2), Fraction(0), Fraction(1)) == Fraction(1)\n        assert clamp(Fraction(-1, 2), Fraction(0), Fraction(1)) == Fraction(0)\n\n    def test_decimals(self):\n        assert clamp(Decimal(\"1.5\"), Decimal(\"0\"), Decimal(\"2\")) == Decimal(\"1.5\")\n        assert clamp(Decimal(\"2.5\"), Decimal(\"0\"), Decimal(\"2\")) == Decimal(\"2\")\n        assert clamp(Decimal(\"-1\"), Decimal(\"0\"), Decimal(\"2\")) == Decimal(\"0\")\n\n    def test_booleans(self):\n        assert clamp(True, False, True) is True\n        assert clamp(False, False, True) is False\n\n    def test_large_integers(self):\n        low, high = 10**30, 10**40\n        assert clamp(10**35, low, high) == 10**35\n        assert clamp(10**50, low, high) == high\n        assert clamp(1, low, high) == low\n\n    def test_only_lt_and_gt_are_required(self):\n        value = OnlyOrdered(5)\n        assert clamp(value, OnlyOrdered(1), OnlyOrdered(10)) is value\n\n    def test_only_lt_and_gt_are_required_for_clamping(self):\n        low = OnlyOrdered(1)\n        high = OnlyOrdered(10)\n        assert clamp(OnlyOrdered(0), low, high) is low\n        assert clamp(OnlyOrdered(99), low, high) is high\n\n    def test_only_gt_is_required_for_range_validation(self):\n        with pytest.raises(ValueError):\n            clamp(OnlyOrdered(5), OnlyOrdered(10), OnlyOrdered(1))\n\n\nclass TestProperties:\n    @pytest.mark.parametrize(\"value, low, high\", INT_GRID)\n    def test_matches_min_max_reference(self, value, low, high):\n        assert clamp(value, low, high) == min(max(value, low), high)\n\n    @pytest.mark.parametrize(\"value, low, high\", INT_GRID)\n    def test_result_is_always_within_bounds(self, value, low, high):\n        result = clamp(value, low, high)\n        assert low <= result <= high\n\n    @pytest.mark.parametrize(\"value, low, high\", INT_GRID)\n    def test_is_idempotent(self, value, low, high):\n        once = clamp(value, low, high)\n        assert clamp(once, low, high) == once\n\n    @pytest.mark.parametrize(\"value, low, high\", INT_GRID)\n    def test_result_equals_value_exactly_when_value_is_in_range(self, value, low, high):\n        assert (clamp(value, low, high) == value) == (low <= value <= high)\n\n    @pytest.mark.parametrize(\"low, high\", [(lo, hi) for lo, hi in itertools.product(range(-2, 3), repeat=2) if lo <= hi])\n    def test_is_monotonic_non_decreasing(self, low, high):\n        values = list(range(-5, 6))\n        results = [clamp(v, low, high) for v in values]\n        assert results == sorted(results)\n\n\nclass TestPurityAndApi:\n    def test_does_not_mutate_its_arguments(self):\n        value, low, high = [0], [1], [10]\n        clamp(value, low, high)\n        assert (value, low, high) == ([0], [1], [10])\n\n    def test_repeated_calls_are_stable(self):\n        assert clamp(11, 1, 10) == clamp(11, 1, 10) == 10\n\n    def test_accepts_keyword_arguments(self):\n        assert clamp(value=5, low=1, high=10) == 5\n        assert clamp(11, low=1, high=10) == 10\n        assert clamp(0, 1, high=10) == 1\n\n    def test_requires_all_three_arguments(self):\n        with pytest.raises(TypeError):\n            clamp(5, 1)\n        with pytest.raises(TypeError):\n            clamp(5)\n        with pytest.raises(TypeError):\n            clamp()\n\n    def test_rejects_extra_positional_arguments(self):\n        with pytest.raises(TypeError):\n            clamp(5, 1, 10, 20)\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-opus-5",
   "subject": "parse_range",
   "status": "ok",
   "tests": 100,
   "mutants": 2,
   "killed": [
    "no_order_check",
    "inclusive_off_by_one"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import pytest\n\nfrom subject import parse_range\n\n\nVALID_CASES = [\n    (\"1-5\", (1, 5)),\n    (\"0-0\", (0, 0)),\n    (\"3-3\", (3, 3)),\n    (\"9-10\", (9, 10)),\n    (\"1-1000000\", (1, 1000000)),\n    (\"007-010\", (7, 10)),\n    (\"+1-+5\", (1, 5)),\n    (\"0-99999999999999999999999999\", (0, 99999999999999999999999999)),\n]\n\nWHITESPACE_CASES = [\n    (\" 1 - 5 \", (1, 5)),\n    (\"\\t2-8\\n\", (2, 8)),\n    (\"3 -  9\", (3, 9)),\n]\n\nWRONG_PART_COUNT = [\n    \"\",\n    \"1\",\n    \"15\",\n    \"abc\",\n    \"1 5\",\n    \"1:5\",\n    \"1,5\",\n    \"1\\u20135\",\n    \"1\\u22125\",\n    \"1-2-3\",\n    \"1-2-3-4\",\n    \"1--5\",\n    \"-1-5\",\n    \"1-5-\",\n    \"--\",\n]\n\nNON_INTEGER_PARTS = [\n    \"-\",\n    \"-5\",\n    \"1-\",\n    \" - \",\n    \"a-b\",\n    \"1-b\",\n    \"a-5\",\n    \"one-five\",\n    \"1.0-5\",\n    \"1-5.0\",\n    \"1e2-5\",\n    \"0x1-0x5\",\n    \"1-5x\",\n    \"+-5\",\n]\n\nOUT_OF_ORDER = [\"5-1\", \"1-0\", \"2-1\", \"10-9\", \"1000000-999999\"]\n\n\nclass TestValidInput:\n    @pytest.mark.parametrize(\"text,expected\", VALID_CASES)\n    def test_returns_expected_pair(self, text, expected):\n        assert parse_range(text) == expected\n\n    @pytest.mark.parametrize(\"text,expected\", WHITESPACE_CASES)\n    def test_surrounding_whitespace_is_tolerated(self, text, expected):\n        assert parse_range(text) == expected\n\n    @pytest.mark.parametrize(\"text\", [case[0] for case in VALID_CASES + WHITESPACE_CASES])\n    def test_result_is_a_two_element_tuple_of_ints(self, text):\n        result = parse_range(text)\n        assert type(result) is tuple\n        assert len(result) == 2\n        assert all(type(value) is int for value in result)\n\n    def test_order_is_start_then_end(self):\n        assert parse_range(\"1-5\") == (1, 5)\n        assert parse_range(\"1-5\") != (5, 1)\n\n    def test_equal_bounds_are_allowed(self):\n        assert parse_range(\"4-4\") == (4, 4)\n\n    def test_repeated_calls_return_the_same_result(self):\n        assert parse_range(\"2-7\") == parse_range(\"2-7\") == (2, 7)\n\n\nclass TestOrderingRule:\n    @pytest.mark.parametrize(\"text\", OUT_OF_ORDER)\n    def test_start_after_end_is_rejected(self, text):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(text)\n        assert excinfo.value.args == (\"start after end\",)\n\n    def test_comparison_is_numeric_not_lexicographic(self):\n        assert parse_range(\"9-10\") == (9, 10)\n        with pytest.raises(ValueError, match=\"start after end\"):\n            parse_range(\"10-9\")\n\n    @pytest.mark.parametrize(\"end\", range(4))\n    @pytest.mark.parametrize(\"start\", range(4))\n    def test_boundary_grid(self, start, end):\n        text = \"{}-{}\".format(start, end)\n        if start <= end:\n            assert parse_range(text) == (start, end)\n        else:\n            with pytest.raises(ValueError, match=\"start after end\"):\n                parse_range(text)\n\n\nclass TestPartCountRule:\n    @pytest.mark.parametrize(\"text\", WRONG_PART_COUNT)\n    def test_wrong_number_of_parts_is_rejected(self, text):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(text)\n        assert excinfo.value.args == (\"expected two parts\",)\n\n    def test_only_ascii_hyphen_is_a_separator(self):\n        with pytest.raises(ValueError, match=\"expected two parts\"):\n            parse_range(\"1\\u20135\")\n        assert parse_range(\"1-5\") == (1, 5)\n\n\nclass TestNonIntegerParts:\n    @pytest.mark.parametrize(\"text\", NON_INTEGER_PARTS)\n    def test_unparsable_part_raises_value_error(self, text):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(text)\n        message = str(excinfo.value)\n        assert \"invalid literal for int()\" in message\n        assert \"expected two parts\" not in message\n        assert \"start after end\" not in message\n\n    def test_negative_bounds_are_not_supported(self):\n        with pytest.raises(ValueError):\n            parse_range(\"-5--1\")\n        with pytest.raises(ValueError):\n            parse_range(\"-5-1\")\n\n\nclass TestCheckOrdering:\n    def test_part_count_is_checked_before_integer_conversion(self):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(\"a-b-c\")\n        assert excinfo.value.args == (\"expected two parts\",)\n\n    def test_part_count_is_checked_before_ordering(self):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(\"9-8-7\")\n        assert excinfo.value.args == (\"expected two parts\",)\n\n    def test_integer_conversion_is_checked_before_ordering(self):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(\"9-abc\")\n        assert \"invalid literal for int()\" in str(excinfo.value)\n        assert \"start after end\" not in str(excinfo.value)\n\n\nclass TestExceptionTypes:\n    @pytest.mark.parametrize(\"text\", [\"1\", \"1-2-3\", \"5-1\", \"a-b\"])\n    def test_raises_plain_value_error(self, text):\n        with pytest.raises(ValueError) as excinfo:\n            parse_range(text)\n        assert type(excinfo.value) is ValueError\n\n    @pytest.mark.parametrize(\n        \"value\",\n        [None, 15, 1.5, True, [\"1\", \"5\"], (\"1\", \"5\"), {\"1\": \"5\"}, {\"1-5\"}, object()],\n    )\n    def test_non_string_input_raises_attribute_error(self, value):\n        with pytest.raises(AttributeError):\n            parse_range(value)\n\n    @pytest.mark.parametrize(\"value\", [b\"1-5\", bytearray(b\"1-5\")])\n    def test_bytes_input_raises_type_error(self, value):\n        with pytest.raises(TypeError):\n            parse_range(value)\n\n    def test_requires_exactly_one_argument(self):\n        with pytest.raises(TypeError):\n            parse_range()\n        with pytest.raises(TypeError):\n            parse_range(\"1-5\", \"2-6\")\n\n\nclass TestIntConversionQuirks:\n    def test_underscore_separators_are_accepted_by_int(self):\n        assert parse_range(\"1_0-2_0\") == (10, 20)\n\n    def test_non_ascii_decimal_digits_are_accepted_by_int(self):\n        assert parse_range(\"\\u0663-\\u0665\") == (3, 5)\n\n    def test_leading_zeros_do_not_change_value(self):\n        assert parse_range(\"0005-0010\") == (5, 10)\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-opus-5",
   "subject": "chunk",
   "status": "ok",
   "tests": 340,
   "mutants": 2,
   "killed": [
    "drops_remainder",
    "allows_zero_size"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import math\n\nimport pytest\n\nfrom subject import chunk\n\n\nclass TestChunkResults:\n    @pytest.mark.parametrize(\n        \"items, size, expected\",\n        [\n            ([], 1, []),\n            ([], 5, []),\n            ([1], 1, [[1]]),\n            ([1], 4, [[1]]),\n            ([1, 2, 3, 4], 2, [[1, 2], [3, 4]]),\n            ([1, 2, 3, 4, 5], 2, [[1, 2], [3, 4], [5]]),\n            ([1, 2, 3, 4, 5, 6], 3, [[1, 2, 3], [4, 5, 6]]),\n            ([1, 2, 3, 4, 5, 6, 7], 3, [[1, 2, 3], [4, 5, 6], [7]]),\n            ([1, 2, 3], 1, [[1], [2], [3]]),\n            ([1, 2, 3], 3, [[1, 2, 3]]),\n            ([1, 2, 3], 4, [[1, 2, 3]]),\n            ([1, 2, 3, 4], 3, [[1, 2, 3], [4]]),\n            ([\"a\", \"b\", \"c\"], 2, [[\"a\", \"b\"], [\"c\"]]),\n            ([None, None, None], 2, [[None, None], [None]]),\n        ],\n    )\n    def test_returns_expected_chunks(self, items, size, expected):\n        assert chunk(items, size) == expected\n\n    def test_returns_a_list(self):\n        assert isinstance(chunk([1, 2, 3], 2), list)\n\n    def test_every_chunk_is_a_list_for_list_input(self):\n        for piece in chunk([1, 2, 3, 4, 5], 2):\n            assert isinstance(piece, list)\n\n    @pytest.mark.parametrize(\"length\", range(0, 12))\n    @pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 7])\n    def test_chunk_count_matches_ceiling_division(self, length, size):\n        items = list(range(length))\n        assert len(chunk(items, size)) == math.ceil(length / size)\n\n    @pytest.mark.parametrize(\"length\", range(0, 12))\n    @pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 7])\n    def test_flattening_restores_the_original_sequence(self, length, size):\n        items = list(range(length))\n        flattened = [value for piece in chunk(items, size) for value in piece]\n        assert flattened == items\n\n    @pytest.mark.parametrize(\"length\", range(1, 12))\n    @pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 7])\n    def test_all_chunks_but_the_last_are_full(self, length, size):\n        result = chunk(list(range(length)), size)\n        for piece in result[:-1]:\n            assert len(piece) == size\n\n    @pytest.mark.parametrize(\"length\", range(1, 12))\n    @pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 7])\n    def test_last_chunk_is_non_empty_and_not_oversized(self, length, size):\n        last = chunk(list(range(length)), size)[-1]\n        assert 1 <= len(last) <= size\n\n    @pytest.mark.parametrize(\"length\", range(0, 12))\n    @pytest.mark.parametrize(\"size\", [1, 2, 3, 4, 7])\n    def test_no_chunk_is_empty(self, length, size):\n        assert all(piece for piece in chunk(list(range(length)), size))\n\n    def test_chunks_do_not_overlap(self):\n        result = chunk([0, 1, 2, 3, 4, 5, 6], 3)\n        assert result == [[0, 1, 2], [3, 4, 5], [6]]\n        seen = [value for piece in result for value in piece]\n        assert len(seen) == len(set(seen))\n\n    def test_duplicate_values_are_preserved(self):\n        assert chunk([1, 1, 1, 1, 1], 2) == [[1, 1], [1, 1], [1]]\n\n    def test_large_input_is_split_correctly(self):\n        items = list(range(1000))\n        result = chunk(items, 7)\n        assert len(result) == 143\n        assert result[0] == list(range(7))\n        assert result[-1] == [994, 995, 996, 997, 998, 999]\n\n    def test_size_much_larger_than_input(self):\n        assert chunk([1, 2, 3], 10 ** 6) == [[1, 2, 3]]\n\n\nclass TestValidation:\n    @pytest.mark.parametrize(\"size\", [0, -1, -2, -10, -(10 ** 6)])\n    def test_non_positive_size_raises_value_error(self, size):\n        with pytest.raises(ValueError):\n            chunk([1, 2, 3], size)\n\n    @pytest.mark.parametrize(\"size\", [0, -1, -5])\n    def test_error_message(self, size):\n        with pytest.raises(ValueError, match=r\"^size must be positive$\"):\n            chunk([1, 2, 3], size)\n\n    @pytest.mark.parametrize(\"size\", [0, -1])\n    def test_validation_runs_even_for_empty_input(self, size):\n        with pytest.raises(ValueError):\n            chunk([], size)\n\n    def test_size_one_is_valid(self):\n        assert chunk([1, 2], 1) == [[1], [2]]\n\n    def test_validation_happens_before_touching_items(self):\n        class Exploding:\n            def __len__(self):\n                raise AssertionError(\"items must not be inspected\")\n\n            def __getitem__(self, item):\n                raise AssertionError(\"items must not be inspected\")\n\n        with pytest.raises(ValueError):\n            chunk(Exploding(), 0)\n\n\nclass TestOtherSequenceTypes:\n    def test_string_is_chunked_into_strings(self):\n        assert chunk(\"abcde\", 2) == [\"ab\", \"cd\", \"e\"]\n\n    def test_string_exact_division(self):\n        assert chunk(\"abcd\", 2) == [\"ab\", \"cd\"]\n\n    def test_empty_string(self):\n        assert chunk(\"\", 3) == []\n\n    def test_tuple_is_chunked_into_tuples(self):\n        result = chunk((1, 2, 3, 4, 5), 2)\n        assert result == [(1, 2), (3, 4), (5,)]\n        assert all(isinstance(piece, tuple) for piece in result)\n\n    def test_bytes_are_chunked_into_bytes(self):\n        assert chunk(b\"abcde\", 2) == [b\"ab\", b\"cd\", b\"e\"]\n\n    def test_range_is_chunked_into_ranges(self):\n        assert chunk(range(5), 2) == [range(0, 2), range(2, 4), range(4, 5)]\n\n\nclass TestInputIsNotMutated:\n    def test_original_list_is_unchanged(self):\n        items = [1, 2, 3, 4, 5]\n        original = list(items)\n        chunk(items, 2)\n        assert items == original\n\n    def test_chunks_are_new_objects(self):\n        items = [1, 2, 3]\n        result = chunk(items, 3)\n        assert result[0] == items\n        assert result[0] is not items\n\n    def test_mutating_a_chunk_does_not_affect_the_input(self):\n        items = [1, 2, 3, 4]\n        result = chunk(items, 2)\n        result[0].append(99)\n        assert items == [1, 2, 3, 4]\n\n    def test_repeated_calls_return_independent_results(self):\n        items = [1, 2, 3, 4]\n        first = chunk(items, 2)\n        second = chunk(items, 2)\n        assert first == second\n        assert first is not second\n        assert first[0] is not second[0]\n\n    def test_element_identity_is_preserved(self):\n        inner = [1, 2]\n        items = [inner, [3, 4], [5, 6]]\n        result = chunk(items, 2)\n        assert result[0][0] is inner\n\n\nclass TestUnsupportedInputs:\n    def test_iterator_input_raises_type_error(self):\n        with pytest.raises(TypeError):\n            chunk(iter([1, 2, 3]), 2)\n\n    def test_none_input_raises_type_error(self):\n        with pytest.raises(TypeError):\n            chunk(None, 2)\n\n    def test_set_input_raises_type_error(self):\n        with pytest.raises(TypeError):\n            chunk({1, 2, 3}, 2)\n\n    @pytest.mark.parametrize(\"size\", [1.5, 2.0, 0.5])\n    def test_non_integer_size_raises_type_error(self, size):\n        with pytest.raises(TypeError):\n            chunk([1, 2, 3], size)\n\n    def test_non_numeric_size_raises_type_error(self):\n        with pytest.raises(TypeError):\n            chunk([1, 2, 3], \"2\")\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-opus-5",
   "subject": "normalise_spaces",
   "status": "ok",
   "tests": 247,
   "mutants": 2,
   "killed": [
    "only_strips_ends",
    "collapses_but_keeps_tabs"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "import random\nimport string\n\nimport pytest\n\nfrom subject import normalise_spaces\n\n\nASCII_WHITESPACE = [\"\\t\", \"\\n\", \"\\v\", \"\\f\", \"\\r\", \" \"]\n\nUNICODE_WHITESPACE = [\"\\xa0\", \"\\u2002\", \"\\u2003\", \"\\u2009\", \"\\u3000\"]\n\nZERO_WIDTH_NON_WHITESPACE = [\"\\u200b\", \"\\u2060\", \"\\ufeff\"]\n\nSAMPLES = [\n    \"\",\n    \" \",\n    \"\\t\\n\\r \",\n    \"word\",\n    \"  word  \",\n    \"two words\",\n    \"a  b\",\n    \"a \\t b \\n c\",\n    \"\\n\\nleading and trailing\\r\\n\",\n    \"many     spaces     between     words\",\n    \"punctuation , stays ; put !\",\n    \"\\u00e9l\\u00e8ve  caf\\u00e9\",\n    \"\\u4f60\\u597d  \\u4e16\\u754c\",\n    \"tabs\\tand\\nnewlines\\tmixed\",\n    \"nbsp\\xa0\\xa0between\\xa0\",\n    \"zero\\u200bwidth  kept\",\n    \"1  2  3  4  5\",\n]\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\"\", \"\"),\n        (\"word\", \"word\"),\n        (\"two words\", \"two words\"),\n        (\"a b c d e\", \"a b c d e\"),\n        (\"  leading\", \"leading\"),\n        (\"trailing  \", \"trailing\"),\n        (\"   both sides   \", \"both sides\"),\n        (\"a  b\", \"a b\"),\n        (\"a          b\", \"a b\"),\n        (\"  a  b  \", \"a b\"),\n        (\"\\ttabbed\\tword\\t\", \"tabbed word\"),\n        (\"\\nnewline\\nseparated\\n\", \"newline separated\"),\n        (\"carriage\\r\\nreturn\", \"carriage return\"),\n        (\"form\\ffeed\", \"form feed\"),\n        (\"vertical\\vtab\", \"vertical tab\"),\n        (\"mixed \\t\\n \\r\\n  whitespace\", \"mixed whitespace\"),\n        (\"multi\\n\\n\\nline\\n\\n\\ntext\", \"multi line text\"),\n        (\"hello, world!  (again)\", \"hello, world! (again)\"),\n        (\"\\u00e9l\\u00e8ve  caf\\u00e9\", \"\\u00e9l\\u00e8ve caf\\u00e9\"),\n        (\"\\t\\n  the   quick \\r brown\\f\\ffox  \\v\", \"the quick brown fox\"),\n    ],\n)\ndef test_collapses_whitespace_runs_and_strips_edges(text, expected):\n    assert normalise_spaces(text) == expected\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"\",\n        \" \",\n        \"   \",\n        \"\\t\",\n        \"\\n\",\n        \"\\r\",\n        \"\\v\",\n        \"\\f\",\n        \"\\r\\n\",\n        \" \\t\\n\\r\\v\\f \",\n        \"\\xa0\",\n        \"\\u3000\",\n        \"\\u2003\\u2009\",\n    ],\n)\ndef test_whitespace_only_input_becomes_empty_string(text):\n    assert normalise_spaces(text) == \"\"\n\n\n@pytest.mark.parametrize(\"word\", [\"a\", \"word\", \"CamelCase\", \"with-hyphen\", \"1234\", \"\\u00e7a\"])\ndef test_single_word_survives_untouched(word):\n    assert normalise_spaces(word) == word\n    assert normalise_spaces(\"   \" + word + \"   \") == word\n\n\ndef test_single_spaces_between_words_are_left_alone():\n    text = \"the quick brown fox jumps over the lazy dog\"\n    assert normalise_spaces(text) == text\n\n\n@pytest.mark.parametrize(\"space\", ASCII_WHITESPACE)\n@pytest.mark.parametrize(\"count\", [1, 2, 5])\ndef test_ascii_whitespace_runs_collapse_to_one_space(space, count):\n    assert normalise_spaces(\"left\" + space * count + \"right\") == \"left right\"\n\n\n@pytest.mark.parametrize(\"space\", UNICODE_WHITESPACE)\n@pytest.mark.parametrize(\"count\", [1, 3])\ndef test_unicode_whitespace_runs_collapse_to_one_ascii_space(space, count):\n    assert normalise_spaces(\"left\" + space * count + \"right\") == \"left right\"\n\n\n@pytest.mark.parametrize(\"space\", UNICODE_WHITESPACE)\ndef test_unicode_whitespace_is_stripped_from_the_edges(space):\n    assert normalise_spaces(space + \"word\" + space) == \"word\"\n\n\n@pytest.mark.parametrize(\"char\", ZERO_WIDTH_NON_WHITESPACE)\ndef test_zero_width_characters_are_not_treated_as_whitespace(char):\n    assert normalise_spaces(\"left\" + char + \"right\") == \"left\" + char + \"right\"\n    assert normalise_spaces(\"  \" + char + \"  \") == char\n\n\ndef test_mixed_whitespace_kinds_in_one_run_collapse_together():\n    assert normalise_spaces(\"a \\t\\n\\xa0\\u3000 b\") == \"a b\"\n\n\ndef test_multiline_text_is_flattened_to_one_line():\n    text = \"first line\\n  second line  \\n\\n\\tthird line\\n\"\n    assert normalise_spaces(text) == \"first line second line third line\"\n    assert \"\\n\" not in normalise_spaces(text)\n\n\n@pytest.mark.parametrize(\"text\", SAMPLES)\ndef test_words_and_their_order_are_preserved(text):\n    assert normalise_spaces(text).split() == text.split()\n\n\n@pytest.mark.parametrize(\"text\", SAMPLES)\ndef test_result_has_no_leading_or_trailing_whitespace(text):\n    result = normalise_spaces(text)\n    assert result == result.strip()\n\n\n@pytest.mark.parametrize(\"text\", SAMPLES)\ndef test_result_only_ever_contains_single_ascii_space_separators(text):\n    result = normalise_spaces(text)\n    assert \"  \" not in result\n    assert all(char == \" \" for char in result if char.isspace())\n\n\n@pytest.mark.parametrize(\"text\", SAMPLES)\ndef test_is_idempotent(text):\n    once = normalise_spaces(text)\n    assert normalise_spaces(once) == once\n\n\n@pytest.mark.parametrize(\"text\", SAMPLES)\ndef test_returns_a_string(text):\n    assert isinstance(normalise_spaces(text), str)\n\n\n@pytest.mark.parametrize(\"text\", SAMPLES)\ndef test_does_not_mutate_its_argument(text):\n    original = text\n    normalise_spaces(text)\n    assert text == original\n\n\ndef test_str_subclass_input_returns_a_plain_str():\n    class Text(str):\n        pass\n\n    result = normalise_spaces(Text(\"a  b\"))\n    assert result == \"a b\"\n    assert type(result) is str\n\n\n@pytest.mark.parametrize(\"value\", [None, 0, 42, 3.5, True, [], [\"a\", \"b\"], {}, {\"a\": 1}, set(), object()])\ndef test_non_string_input_raises_attribute_error(value):\n    with pytest.raises(AttributeError):\n        normalise_spaces(value)\n\n\n@pytest.mark.parametrize(\"value\", [b\"a  b\", bytearray(b\"a  b\")])\ndef test_bytes_like_input_raises_type_error(value):\n    with pytest.raises(TypeError):\n        normalise_spaces(value)\n\n\ndef test_missing_argument_raises_type_error():\n    with pytest.raises(TypeError):\n        normalise_spaces()\n\n\ndef test_handles_a_large_input():\n    words = [\"word%d\" % index for index in range(10000)]\n    text = \"\".join(\"  \\t \" + word + \" \\n \" for word in words)\n    assert normalise_spaces(text) == \" \".join(words)\n\n\ndef test_handles_a_long_run_of_whitespace_between_two_words():\n    assert normalise_spaces(\"start\" + \" \" * 100000 + \"end\") == \"start end\"\n\n\n@pytest.mark.parametrize(\"seed\", range(50))\ndef test_matches_reference_behaviour_on_random_input(seed):\n    rng = random.Random(seed)\n    alphabet = string.ascii_letters + string.digits + \"!?,.-_\"\n    whitespace = ASCII_WHITESPACE + UNICODE_WHITESPACE\n\n    words = [\n        \"\".join(rng.choice(alphabet) for _ in range(rng.randint(1, 8)))\n        for _ in range(rng.randint(0, 10))\n    ]\n\n    def gap(minimum):\n        return \"\".join(rng.choice(whitespace) for _ in range(rng.randint(minimum, 4)))\n\n    text = gap(0)\n    for index, word in enumerate(words):\n        if index:\n            text += gap(1)\n        text += word\n    text += gap(0)\n\n    assert normalise_spaces(text) == \" \".join(words)\n",
   "detail": null
  },
  {
   "round": "r2",
   "model": "claude-opus-5",
   "subject": "percent_change",
   "status": "ok",
   "tests": 70,
   "mutants": 2,
   "killed": [
    "wrong_denominator",
    "forgets_percent"
   ],
   "survived": [],
   "kill_count": 2,
   "tests_passed": null,
   "tests_failed": null,
   "test_src": "from decimal import Decimal\n\nimport pytest\n\nfrom subject import percent_change\n\n\n@pytest.mark.parametrize(\n    \"old, new, expected\",\n    [\n        (100, 150, 50.0),\n        (100, 50, -50.0),\n        (100, 200, 100.0),\n        (100, 300, 200.0),\n        (100, 0, -100.0),\n        (100, 100, 0.0),\n        (200, 250, 25.0),\n        (8, 2, -75.0),\n        (4, 5, 25.0),\n        (1, 3, 200.0),\n        (3, 1, -200.0 / 3),\n        (2.5, 5.0, 100.0),\n        (0.5, 0.75, 50.0),\n        (0.2, 0.1, -50.0),\n        (-100, -50, -50.0),\n        (-100, -150, 50.0),\n        (-50, 50, -200.0),\n        (50, -50, -200.0),\n        (-4, 0, -100.0),\n        (-2, -2, 0.0),\n        (1000000, 1000001, 0.0001),\n    ],\n)\ndef test_returns_expected_percentage(old, new, expected):\n    assert percent_change(old, new) == pytest.approx(expected)\n\n\ndef test_result_is_exact_for_representable_values():\n    assert percent_change(200, 250) == 25.0\n    assert percent_change(100, 150) == 50.0\n    assert percent_change(100, 50) == -50.0\n\n\ndef test_result_is_a_float():\n    assert isinstance(percent_change(4, 5), float)\n    assert isinstance(percent_change(4, 4), float)\n\n\n@pytest.mark.parametrize(\"value\", [1, 7, -3, 2.5, -0.75, 10**9])\ndef test_unchanged_value_is_zero_percent(value):\n    assert percent_change(value, value) == 0.0\n\n\ndef test_doubling_is_one_hundred_percent():\n    assert percent_change(7, 14) == pytest.approx(100.0)\n\n\ndef test_halving_is_minus_fifty_percent():\n    assert percent_change(7, 3.5) == pytest.approx(-50.0)\n\n\ndef test_increase_is_positive_and_decrease_is_negative_for_positive_old():\n    assert percent_change(10, 12) > 0\n    assert percent_change(10, 8) < 0\n\n\ndef test_sign_is_relative_to_a_negative_baseline():\n    assert percent_change(-10, -12) > 0\n    assert percent_change(-10, -8) < 0\n\n\n@pytest.mark.parametrize(\"old, new\", [(2, 9), (-3, 4), (1.5, -2.5), (10, 10), (0.25, 0.3)])\ndef test_matches_independent_ratio_formula(old, new):\n    assert percent_change(old, new) == pytest.approx((new / old - 1) * 100)\n\n\n@pytest.mark.parametrize(\"scale\", [2, 10, 0.5, -3])\ndef test_scaling_both_arguments_leaves_the_result_unchanged(scale):\n    assert percent_change(3 * scale, 7 * scale) == pytest.approx(percent_change(3, 7))\n\n\ndef test_magnitude_grows_as_new_moves_further_away():\n    assert percent_change(10, 11) < percent_change(10, 12) < percent_change(10, 20)\n    assert percent_change(10, 9) > percent_change(10, 8) > percent_change(10, 0)\n\n\ndef test_very_small_baseline_still_computes():\n    assert percent_change(1e-12, 2e-12) == pytest.approx(100.0)\n\n\ndef test_very_large_baseline_still_computes():\n    assert percent_change(10**15, 3 * 10**15) == pytest.approx(200.0)\n\n\n@pytest.mark.parametrize(\"new\", [-10, -0.5, 0, 0.5, 10])\n@pytest.mark.parametrize(\"old\", [0, 0.0, -0.0])\ndef test_zero_baseline_raises_zero_division_error(old, new):\n    with pytest.raises(ZeroDivisionError):\n        percent_change(old, new)\n\n\ndef test_zero_baseline_error_message():\n    with pytest.raises(ZeroDivisionError, match=\"old must not be zero\"):\n        percent_change(0, 5)\n\n\n@pytest.mark.parametrize(\"old\", [1, -1, 0.001, -0.001, 1e-15])\ndef test_non_zero_baseline_does_not_raise(old):\n    percent_change(old, 5)\n\n\ndef test_decimal_inputs_are_computed_exactly():\n    assert percent_change(Decimal(\"100\"), Decimal(\"150\")) == Decimal(\"50\")\n    assert percent_change(Decimal(\"0.3\"), Decimal(\"0.6\")) == Decimal(\"100\")\n\n\ndef test_decimal_zero_baseline_raises_zero_division_error():\n    with pytest.raises(ZeroDivisionError):\n        percent_change(Decimal(\"0\"), Decimal(\"5\"))\n\n\ndef test_non_numeric_input_raises_type_error():\n    with pytest.raises(TypeError):\n        percent_change(\"100\", \"150\")\n\n\ndef test_arguments_are_not_swapped():\n    assert percent_change(50, 100) == pytest.approx(100.0)\n    assert percent_change(100, 50) == pytest.approx(-50.0)\n",
   "detail": null
  }
 ],
 "results": {
  "headline": "35 of 36 catchable mutants killed across 20 generated suites.",
  "suite_size_correction_2026_08_21": {
   "what_was_published": "a median of 196 test cases per function for Opus 5 against 11 for Haiku 4.5",
   "why_both_figures_were_wrong": "196 is a PHANTOM: with n=10 the median is the midpoint of the 5th and 6th values, 145 and 247, and no run produced 196. The Haiku figure was worse than a phantom, it was simply not the median: over the 6 usable Haiku suites the median is 11.5, not 11.",
   "what_replaces_them": "the observed ranges and their non-overlap, which need no estimator at all: every Opus suite is larger than every Haiku suite.",
   "opus_5_test_cases": {
    "n": 10,
    "values": [
     70,
     71,
     100,
     128,
     145,
     247,
     305,
     340,
     350,
     875
    ],
    "min": 70,
    "max": 875
   },
   "haiku_4_5_test_cases": {
    "n_usable": 6,
    "values": [
     8,
     9,
     11,
     12,
     13,
     13
    ],
    "min": 8,
    "max": 13,
    "note": "4 of Haiku's 10 runs produced no scorable suite and have no test count."
   },
   "arms_overlap": false,
   "smallest_opus_suite_vs_largest_haiku_suite": "70 against 13",
   "medians_if_you_want_them": {
    "opus": 196.0,
    "haiku": 11.5,
    "warning": "both are midpoints of an even sample and neither value was produced by any run, which is exactly why the post no longer leads with them."
   }
  },
  "usable_suites": {
   "opus_5": "10 of 10",
   "haiku_4_5": "6 of 10"
  }
 }
}