mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 00:08:07 +02:00 
			
		
		
		
	 8e2c9f60be
			
		
	
	
		8e2c9f60be
		
	
	
	
	
		
			
			This provides a way to get a list of frequently failing tests, for example with * ./mach intermittents list * ./mach intermittents list --days 7 --threshold 30 --json | jq -r '.[] | select(.test_path) | .test_path' Differential Revision: https://phabricator.services.mozilla.com/D256373
		
			
				
	
	
		
			165 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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
 |