File: intermittents_mach_commands.py

package info (click to toggle)
firefox 144.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,637,504 kB
  • sloc: cpp: 7,576,692; javascript: 6,430,831; ansic: 3,748,119; python: 1,398,978; xml: 628,810; asm: 438,679; java: 186,194; sh: 63,212; makefile: 19,159; objc: 13,086; perl: 12,986; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (165 lines) | stat: -rw-r--r-- 5,006 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import json
import logging
import sys

from intermittent_failures import IntermittentFailuresFetcher
from mach.decorators import Command, CommandArgument, SubCommand


@Command(
    "intermittents",
    category="testing",
    description="Analyze intermittent test failures",
)
def intermittents(command_context):
    """
    Utility to analyze intermittent test failures in Firefox.
    """
    # Print help text when no subcommand is provided
    print("usage: mach intermittents <subcommand> [options]")
    print()
    print("Analyze intermittent test failures in Firefox.")
    print()
    print("subcommands:")
    print("  list    List the most frequent intermittent test failures")
    print()
    print("Run 'mach intermittents <subcommand> --help' for more information.")
    sys.exit(0)


@SubCommand(
    "intermittents",
    "list",
    description="List the most frequent intermittent test failures",
)
@CommandArgument(
    "--days",
    type=int,
    default=7,
    help="Number of days to look back for failures (default: 7)",
)
@CommandArgument(
    "--threshold",
    type=int,
    default=30,
    help="Minimum number of failures to include (default: 30)",
)
@CommandArgument(
    "--branch",
    default="trunk",
    help="Branch to query (default: trunk)",
)
@CommandArgument(
    "--json",
    action="store_true",
    dest="json_output",
    help="Output results as JSON",
)
@CommandArgument(
    "--verbose",
    action="store_true",
    help="Show additional details for each failure",
)
@CommandArgument(
    "--all",
    action="store_true",
    help="Show all bugs (by default only single tracking bugs with test paths are shown)",
)
def list_intermittents(
    command_context,
    days=7,
    threshold=30,
    branch="trunk",
    json_output=False,
    verbose=False,
    all=False,
):
    """List the most frequent intermittent test failures"""

    # Logging setup
    if not json_output:
        command_context.log(
            logging.INFO,
            "intermittents",
            {},
            f"Fetching intermittent failures from the last {days} days with at least {threshold} occurrences...",
        )

    fetcher = IntermittentFailuresFetcher(
        days=days, threshold=threshold, verbose=verbose and not json_output
    )

    try:
        results = fetcher.get_failures(branch=branch)
    except Exception as e:
        command_context.log(
            logging.ERROR,
            "intermittents",
            {"error": str(e)},
            "Error fetching failures: {error}",
        )
        return 1

    if not all:
        results = [
            result
            for result in results
            if result.get("test_path") and "single tracking bug" in result["summary"]
        ]

    if not results:
        if not json_output:
            message = f"No bugs found with at least {threshold} failures in the last {days} days."
            if not all:
                message = f"No single tracking bugs with test paths found with at least {threshold} failures in the last {days} days. Use --all to see all bugs."
            command_context.log(
                logging.INFO,
                "intermittents",
                {},
                message,
            )
        else:
            print(json.dumps([]))
        return 0

    results.sort(key=lambda x: x["failure_count"], reverse=True)

    if json_output:
        print(json.dumps(results, indent=2))
    else:
        command_context.log(
            logging.INFO,
            "intermittents",
            {"count": len(results), "threshold": threshold},
            "Found {count} bugs with at least {threshold} failures:",
        )
        print()

        for i, result in enumerate(results, 1):
            print(f"{i}. Bug {result['bug_id']}: {result['failure_count']} failures")
            if result.get("test_path"):
                print(f"   Test: {result['test_path']}")
            print(f"   Summary: {result['summary']}")
            print(f"   Status: {result['status']}", end="")
            if result.get("resolution"):
                print(f" - {result['resolution']}")
            else:
                print()
            if result.get("creation_time"):
                created = result["creation_time"].split("T")[0]  # Just the date part
                print(f"   Created: {created}")
            if result.get("last_change_time"):
                updated = result["last_change_time"].split("T")[0]  # Just the date part
                print(f"   Last updated: {updated}")
            if result.get("comment_count") is not None:
                print(f"   Comments: {result['comment_count']}")
            print(
                f"   URL: https://bugzilla.mozilla.org/show_bug.cgi?id={result['bug_id']}"
            )
            print()

    return 0