prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  parse_stdout.py produces malformed Junit reports
  parse_stdout.py has recently stopped working.
  This causes Windows timeouts to be reported as a white box instead of a red box in https://dask.org/distributed/test_report.html.
  
  https://github.com/dask/distributed/actions/runs/2293197167
  > Publish test results: artifacts/windows-latest-3.10-notci1/pytest.xml#L976Error processing result file: not well-formed (invalid token): line 976, column 93
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `continuous_integration/scripts/parse_stdout.py`
  Content:
  ```
  1 """On Windows, pytest-timeout kills off the whole test suite, leaving no junit report
  2 behind. Parse the stdout of pytest to generate one.
  3 """
  4 from __future__ import annotations
  5 
  6 import re
  7 import sys
  8 from collections import Counter, defaultdict
  9 from collections.abc import Iterable
  10 from datetime import datetime
  11 
  12 OUTCOMES = {
  13     "PASSED",
  14     "FAILED",
  15     # Test timeout. Marked as a variant of FAILED in the junit report
  16     None,
  17     # Setup failed or teardown failed.
  18     # In the latter case, if the test also failed, show both a FAILED and an ERROR line.
  19     "ERROR",
  20     # @pytest.mark.skip, @pytest.mark.skipif, or raise pytest.skip()
  21     "SKIPPED",
  22     # Reported as a variant of SKIPPED in the junit report
  23     "XFAIL",
  24     # These appear respectively before and after another status. Ignore.
  25     "RERUN",
  26     "LEAKED",
  27 }
  28 
  29 
  30 def parse_rows(rows: Iterable[str]) -> list[tuple[str, str, set[str | None]]]:
  31     match = re.compile(
  32         r"(distributed/.*test.*)::([^ ]*)"
  33         r"( (.*)(PASSED|FAILED|ERROR|SKIPPED|XFAIL|RERUN|LEAKED).*| )$"
  34     )
  35 
  36     out: defaultdict[tuple[str, str], set[str | None]] = defaultdict(set)
  37 
  38     for row in rows:
  39         m = match.match(row)
  40         if not m:
  41             continue
  42 
  43         fname = m.group(1)
  44         clsname = fname.replace("/", ".").replace(".py", "").replace("::", ".")
  45 
  46         tname = m.group(2).strip()
  47         if m.group(4) and "]" in m.group(4):
  48             tname += " " + m.group(4).split("]")[0] + "]"
  49 
  50         outcome = m.group(5)
  51         assert outcome in OUTCOMES
  52         if outcome not in {"RERUN", "LEAKED"}:
  53             out[clsname, tname].add(outcome)
  54 
  55     return [(clsname, tname, outcomes) for (clsname, tname), outcomes in out.items()]
  56 
  57 
  58 def build_xml(rows: list[tuple[str, str, set[str | None]]]) -> None:
  59     cnt = Counter(outcome for _, _, outcomes in rows for outcome in outcomes)
  60     timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")
  61 
  62     # We could have used ElementTree but it feels like overkill here
  63     print('<?xml version="1.0" encoding="utf-8"?>')
  64     print("<testsuites>")
  65     print(
  66         '<testsuite name="distributed" '
  67         f'errors="{cnt["ERROR"]}" failures="{cnt["FAILED"] + cnt[None]}" '
  68         f'skipped="{cnt["SKIPPED"] + cnt["XFAIL"]}" tests="{sum(cnt.values())}" '
  69         f'time="0.0" timestamp="{timestamp}" hostname="">'
  70     )
  71 
  72     for clsname, tname, outcomes in rows:
  73         print(f'<testcase classname="{clsname}" name="{tname}" time="0.0"', end="")
  74         if outcomes == {"PASSED"}:
  75             print(" />")
  76         elif outcomes == {"FAILED"}:
  77             print('><failure message=""></failure></testcase>')
  78         elif outcomes == {None}:
  79             print('><failure message="pytest-timeout exceeded"></failure></testcase>')
  80         elif outcomes == {"ERROR"}:
  81             print('><error message="failed on setup"></error></testcase>')
  82         elif outcomes == {"PASSED", "ERROR"}:
  83             print('><error message="failed on teardown"></error></testcase>')
  84         elif outcomes == {"FAILED", "ERROR"}:
  85             print(
  86                 '><failure message=""></failure></testcase>\n'
  87                 f'<testcase classname="{clsname}" name="{tname}" time="0.0">'
  88                 '<error message="failed on teardown"></error></testcase>'
  89             )
  90         elif outcomes == {"SKIPPED"}:
  91             print('><skipped type="pytest.skip" message="skip"></skipped></testcase>')
  92         elif outcomes == {"XFAIL"}:
  93             print('><skipped type="pytest.xfail" message="xfail"></skipped></testcase>')
  94         else:  # pragma: nocover
  95             # This should be unreachable. We would normally raise ValueError, except
  96             # that a crash in this script would be pretty much invisible.
  97             print(
  98                 f' />\n<testcase classname="parse_stdout" name="build_xml" time="0.0">'
  99                 f'><failure message="Unexpected {outcomes=}"></failure></testcase>'
  100             )
  101 
  102     print("</testsuite>")
  103     print("</testsuites>")
  104 
  105 
  106 def main() -> None:  # pragma: nocover
  107     build_xml(parse_rows(sys.stdin))
  108 
  109 
  110 if __name__ == "__main__":
  111     main()  # pragma: nocover
  112
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/continuous_integration/scripts/parse_stdout.py b/continuous_integration/scripts/parse_stdout.py
  --- a/continuous_integration/scripts/parse_stdout.py
  +++ b/continuous_integration/scripts/parse_stdout.py
  @@ -3,6 +3,7 @@
   """
   from __future__ import annotations
   
  +import html
   import re
   import sys
   from collections import Counter, defaultdict
  @@ -70,6 +71,8 @@
       )
   
       for clsname, tname, outcomes in rows:
  +        clsname = html.escape(clsname)
  +        tname = html.escape(tname)
           print(f'<testcase classname="{clsname}" name="{tname}" time="0.0"', end="")
           if outcomes == {"PASSED"}:
               print(" />")
  
verification_info: {"golden_diff": "diff --git a/continuous_integration/scripts/parse_stdout.py b/continuous_integration/scripts/parse_stdout.py\n--- a/continuous_integration/scripts/parse_stdout.py\n+++ b/continuous_integration/scripts/parse_stdout.py\n@@ -3,6 +3,7 @@\n \"\"\"\n from __future__ import annotations\n \n+import html\n import re\n import sys\n from collections import Counter, defaultdict\n@@ -70,6 +71,8 @@\n     )\n \n     for clsname, tname, outcomes in rows:\n+        clsname = html.escape(clsname)\n+        tname = html.escape(tname)\n         print(f'<testcase classname=\"{clsname}\" name=\"{tname}\" time=\"0.0\"', end=\"\")\n         if outcomes == {\"PASSED\"}:\n             print(\" />\")\n", "issue": "parse_stdout.py produces malformed Junit reports\nparse_stdout.py has recently stopped working.\r\nThis causes Windows timeouts to be reported as a white box instead of a red box in https://dask.org/distributed/test_report.html.\r\n\r\nhttps://github.com/dask/distributed/actions/runs/2293197167\r\n> Publish test results:\u00a0artifacts/windows-latest-3.10-notci1/pytest.xml#L976Error processing result file: not well-formed (invalid token): line 976, column 93\r\n\n", "before_files": [{"content": "\"\"\"On Windows, pytest-timeout kills off the whole test suite, leaving no junit report\nbehind. Parse the stdout of pytest to generate one.\n\"\"\"\nfrom __future__ import annotations\n\nimport re\nimport sys\nfrom collections import Counter, defaultdict\nfrom collections.abc import Iterable\nfrom datetime import datetime\n\nOUTCOMES = {\n    \"PASSED\",\n    \"FAILED\",\n    # Test timeout. Marked as a variant of FAILED in the junit report\n    None,\n    # Setup failed or teardown failed.\n    # In the latter case, if the test also failed, show both a FAILED and an ERROR line.\n    \"ERROR\",\n    # @pytest.mark.skip, @pytest.mark.skipif, or raise pytest.skip()\n    \"SKIPPED\",\n    # Reported as a variant of SKIPPED in the junit report\n    \"XFAIL\",\n    # These appear respectively before and after another status. Ignore.\n    \"RERUN\",\n    \"LEAKED\",\n}\n\n\ndef parse_rows(rows: Iterable[str]) -> list[tuple[str, str, set[str | None]]]:\n    match = re.compile(\n        r\"(distributed/.*test.*)::([^ ]*)\"\n        r\"( (.*)(PASSED|FAILED|ERROR|SKIPPED|XFAIL|RERUN|LEAKED).*| )$\"\n    )\n\n    out: defaultdict[tuple[str, str], set[str | None]] = defaultdict(set)\n\n    for row in rows:\n        m = match.match(row)\n        if not m:\n            continue\n\n        fname = m.group(1)\n        clsname = fname.replace(\"/\", \".\").replace(\".py\", \"\").replace(\"::\", \".\")\n\n        tname = m.group(2).strip()\n        if m.group(4) and \"]\" in m.group(4):\n            tname += \" \" + m.group(4).split(\"]\")[0] + \"]\"\n\n        outcome = m.group(5)\n        assert outcome in OUTCOMES\n        if outcome not in {\"RERUN\", \"LEAKED\"}:\n            out[clsname, tname].add(outcome)\n\n    return [(clsname, tname, outcomes) for (clsname, tname), outcomes in out.items()]\n\n\ndef build_xml(rows: list[tuple[str, str, set[str | None]]]) -> None:\n    cnt = Counter(outcome for _, _, outcomes in rows for outcome in outcomes)\n    timestamp = datetime.now().strftime(\"%Y-%m-%dT%H:%M:%S.%f\")\n\n    # We could have used ElementTree but it feels like overkill here\n    print('<?xml version=\"1.0\" encoding=\"utf-8\"?>')\n    print(\"<testsuites>\")\n    print(\n        '<testsuite name=\"distributed\" '\n        f'errors=\"{cnt[\"ERROR\"]}\" failures=\"{cnt[\"FAILED\"] + cnt[None]}\" '\n        f'skipped=\"{cnt[\"SKIPPED\"] + cnt[\"XFAIL\"]}\" tests=\"{sum(cnt.values())}\" '\n        f'time=\"0.0\" timestamp=\"{timestamp}\" hostname=\"\">'\n    )\n\n    for clsname, tname, outcomes in rows:\n        print(f'<testcase classname=\"{clsname}\" name=\"{tname}\" time=\"0.0\"', end=\"\")\n        if outcomes == {\"PASSED\"}:\n            print(\" />\")\n        elif outcomes == {\"FAILED\"}:\n            print('><failure message=\"\"></failure></testcase>')\n        elif outcomes == {None}:\n            print('><failure message=\"pytest-timeout exceeded\"></failure></testcase>')\n        elif outcomes == {\"ERROR\"}:\n            print('><error message=\"failed on setup\"></error></testcase>')\n        elif outcomes == {\"PASSED\", \"ERROR\"}:\n            print('><error message=\"failed on teardown\"></error></testcase>')\n        elif outcomes == {\"FAILED\", \"ERROR\"}:\n            print(\n                '><failure message=\"\"></failure></testcase>\\n'\n                f'<testcase classname=\"{clsname}\" name=\"{tname}\" time=\"0.0\">'\n                '<error message=\"failed on teardown\"></error></testcase>'\n            )\n        elif outcomes == {\"SKIPPED\"}:\n            print('><skipped type=\"pytest.skip\" message=\"skip\"></skipped></testcase>')\n        elif outcomes == {\"XFAIL\"}:\n            print('><skipped type=\"pytest.xfail\" message=\"xfail\"></skipped></testcase>')\n        else:  # pragma: nocover\n            # This should be unreachable. We would normally raise ValueError, except\n            # that a crash in this script would be pretty much invisible.\n            print(\n                f' />\\n<testcase classname=\"parse_stdout\" name=\"build_xml\" time=\"0.0\">'\n                f'><failure message=\"Unexpected {outcomes=}\"></failure></testcase>'\n            )\n\n    print(\"</testsuite>\")\n    print(\"</testsuites>\")\n\n\ndef main() -> None:  # pragma: nocover\n    build_xml(parse_rows(sys.stdin))\n\n\nif __name__ == \"__main__\":\n    main()  # pragma: nocover\n", "path": "continuous_integration/scripts/parse_stdout.py"}], "after_files": [{"content": "\"\"\"On Windows, pytest-timeout kills off the whole test suite, leaving no junit report\nbehind. Parse the stdout of pytest to generate one.\n\"\"\"\nfrom __future__ import annotations\n\nimport html\nimport re\nimport sys\nfrom collections import Counter, defaultdict\nfrom collections.abc import Iterable\nfrom datetime import datetime\n\nOUTCOMES = {\n    \"PASSED\",\n    \"FAILED\",\n    # Test timeout. Marked as a variant of FAILED in the junit report\n    None,\n    # Setup failed or teardown failed.\n    # In the latter case, if the test also failed, show both a FAILED and an ERROR line.\n    \"ERROR\",\n    # @pytest.mark.skip, @pytest.mark.skipif, or raise pytest.skip()\n    \"SKIPPED\",\n    # Reported as a variant of SKIPPED in the junit report\n    \"XFAIL\",\n    # These appear respectively before and after another status. Ignore.\n    \"RERUN\",\n    \"LEAKED\",\n}\n\n\ndef parse_rows(rows: Iterable[str]) -> list[tuple[str, str, set[str | None]]]:\n    match = re.compile(\n        r\"(distributed/.*test.*)::([^ ]*)\"\n        r\"( (.*)(PASSED|FAILED|ERROR|SKIPPED|XFAIL|RERUN|LEAKED).*| )$\"\n    )\n\n    out: defaultdict[tuple[str, str], set[str | None]] = defaultdict(set)\n\n    for row in rows:\n        m = match.match(row)\n        if not m:\n            continue\n\n        fname = m.group(1)\n        clsname = fname.replace(\"/\", \".\").replace(\".py\", \"\").replace(\"::\", \".\")\n\n        tname = m.group(2).strip()\n        if m.group(4) and \"]\" in m.group(4):\n            tname += \" \" + m.group(4).split(\"]\")[0] + \"]\"\n\n        outcome = m.group(5)\n        assert outcome in OUTCOMES\n        if outcome not in {\"RERUN\", \"LEAKED\"}:\n            out[clsname, tname].add(outcome)\n\n    return [(clsname, tname, outcomes) for (clsname, tname), outcomes in out.items()]\n\n\ndef build_xml(rows: list[tuple[str, str, set[str | None]]]) -> None:\n    cnt = Counter(outcome for _, _, outcomes in rows for outcome in outcomes)\n    timestamp = datetime.now().strftime(\"%Y-%m-%dT%H:%M:%S.%f\")\n\n    # We could have used ElementTree but it feels like overkill here\n    print('<?xml version=\"1.0\" encoding=\"utf-8\"?>')\n    print(\"<testsuites>\")\n    print(\n        '<testsuite name=\"distributed\" '\n        f'errors=\"{cnt[\"ERROR\"]}\" failures=\"{cnt[\"FAILED\"] + cnt[None]}\" '\n        f'skipped=\"{cnt[\"SKIPPED\"] + cnt[\"XFAIL\"]}\" tests=\"{sum(cnt.values())}\" '\n        f'time=\"0.0\" timestamp=\"{timestamp}\" hostname=\"\">'\n    )\n\n    for clsname, tname, outcomes in rows:\n        clsname = html.escape(clsname)\n        tname = html.escape(tname)\n        print(f'<testcase classname=\"{clsname}\" name=\"{tname}\" time=\"0.0\"', end=\"\")\n        if outcomes == {\"PASSED\"}:\n            print(\" />\")\n        elif outcomes == {\"FAILED\"}:\n            print('><failure message=\"\"></failure></testcase>')\n        elif outcomes == {None}:\n            print('><failure message=\"pytest-timeout exceeded\"></failure></testcase>')\n        elif outcomes == {\"ERROR\"}:\n            print('><error message=\"failed on setup\"></error></testcase>')\n        elif outcomes == {\"PASSED\", \"ERROR\"}:\n            print('><error message=\"failed on teardown\"></error></testcase>')\n        elif outcomes == {\"FAILED\", \"ERROR\"}:\n            print(\n                '><failure message=\"\"></failure></testcase>\\n'\n                f'<testcase classname=\"{clsname}\" name=\"{tname}\" time=\"0.0\">'\n                '<error message=\"failed on teardown\"></error></testcase>'\n            )\n        elif outcomes == {\"SKIPPED\"}:\n            print('><skipped type=\"pytest.skip\" message=\"skip\"></skipped></testcase>')\n        elif outcomes == {\"XFAIL\"}:\n            print('><skipped type=\"pytest.xfail\" message=\"xfail\"></skipped></testcase>')\n        else:  # pragma: nocover\n            # This should be unreachable. We would normally raise ValueError, except\n            # that a crash in this script would be pretty much invisible.\n            print(\n                f' />\\n<testcase classname=\"parse_stdout\" name=\"build_xml\" time=\"0.0\">'\n                f'><failure message=\"Unexpected {outcomes=}\"></failure></testcase>'\n            )\n\n    print(\"</testsuite>\")\n    print(\"</testsuites>\")\n\n\ndef main() -> None:  # pragma: nocover\n    build_xml(parse_rows(sys.stdin))\n\n\nif __name__ == \"__main__\":\n    main()  # pragma: nocover\n", "path": "continuous_integration/scripts/parse_stdout.py"}]}