1#!/usr/bin/env python3 2# Copyright © 2020 - 2024 Collabora Ltd. 3# Authors: 4# David Heidelberg <[email protected]> 5# Sergi Blanch Torne <[email protected]> 6# SPDX-License-Identifier: MIT 7 8""" 9Compare the two latest scheduled pipelines and provide information 10about the jobs you're interested in. 11""" 12 13import argparse 14import csv 15import re 16import requests 17import io 18from tabulate import tabulate 19 20import gitlab 21from colorama import Fore, Style 22from gitlab_common import read_token 23 24 25MARGE_BOT_USER_ID = 9716 26 27def print_failures_csv(id): 28 url = 'https://gitlab.freedesktop.org/mesa/mesa/-/jobs/' + str(id) + '/artifacts/raw/results/failures.csv' 29 missing: int = 0 30 MAX_MISS: int = 20 31 try: 32 response = requests.get(url) 33 response.raise_for_status() 34 csv_content = io.StringIO(response.text) 35 csv_reader = csv.reader(csv_content) 36 data = list(csv_reader) 37 38 for line in data[:]: 39 if line[1] == "UnexpectedImprovement(Pass)": 40 line[1] = Fore.GREEN + line[1] + Style.RESET_ALL 41 elif line[1] == "UnexpectedImprovement(Fail)": 42 line[1] = Fore.YELLOW + line[1] + Style.RESET_ALL 43 elif line[1] == "Crash" or line[1] == "Fail": 44 line[1] = Fore.RED + line[1] + Style.RESET_ALL 45 elif line[1] == "Missing": 46 if missing > MAX_MISS: 47 data.remove(line) 48 continue 49 missing += 1 50 line[1] = Fore.YELLOW + line[1] + Style.RESET_ALL 51 elif line[1] == "Fail": 52 line[1] = Fore.RED + line[1] + Style.RESET_ALL 53 else: 54 line[1] = Fore.WHITE + line[1] + Style.RESET_ALL 55 56 if missing > MAX_MISS: 57 data.append([Fore.RED + f"... more than {MAX_MISS} missing tests, something crashed?", "Missing" + Style.RESET_ALL]) 58 headers = ["Test ", "Result"] 59 print(tabulate(data, headers, tablefmt="plain")) 60 except Exception: 61 pass 62 63 64def job_failed_before(old_jobs, job): 65 for old_job in old_jobs: 66 if job.name == old_job.name: 67 return old_job 68 69 70def parse_args() -> None: 71 """Parse args""" 72 parser = argparse.ArgumentParser( 73 description="Tool to show merge requests assigned to the marge-bot", 74 ) 75 parser.add_argument( 76 "--target", 77 metavar="target-job", 78 help="Target job regex. For multiple targets, pass multiple values, " 79 "eg. `--target foo bar`.", 80 required=False, 81 nargs=argparse.ONE_OR_MORE, 82 ) 83 parser.add_argument( 84 "--token", 85 metavar="token", 86 help="force GitLab token, otherwise it's read from ~/.config/gitlab-token", 87 ) 88 return parser.parse_args() 89 90 91if __name__ == "__main__": 92 args = parse_args() 93 token = read_token(args.token) 94 gl = gitlab.Gitlab(url="https://gitlab.freedesktop.org", private_token=token) 95 96 project = gl.projects.get("mesa/mesa") 97 98 print( 99 "\u001b]8;;https://gitlab.freedesktop.org/mesa/mesa/-/pipelines?page=1&scope=all&source=schedule\u001b\\Scheduled pipelines overview\u001b]8;;\u001b\\" 100 ) 101 pipelines = project.pipelines.list( 102 source="schedule", ordered_by="created_at", sort="desc", page=1, per_page=2 103 ) 104 print( 105 f"Old pipeline: {pipelines[1].created_at}\t\u001b]8;;{pipelines[1].web_url}\u001b\\{pipelines[1].status}\u001b]8;;\u001b\\\t{pipelines[1].sha}" 106 ) 107 print( 108 f"New pipeline: {pipelines[0].created_at}\t\u001b]8;;{pipelines[0].web_url}\u001b\\{pipelines[0].status}\u001b]8;;\u001b\\\t{pipelines[0].sha}" 109 ) 110 print( 111 f"\nWebUI visual compare: https://gitlab.freedesktop.org/mesa/mesa/-/compare/{pipelines[1].sha}...{pipelines[0].sha}\n" 112 ) 113 114 # regex part 115 if args.target: 116 target = "|".join(args.target) 117 target = target.strip() 118 print(" jobs: " + Fore.BLUE + target + Style.RESET_ALL) 119 120 target = f"({target})" + r"( \d+/\d+)?" 121 else: 122 target = ".*" 123 124 target_jobs_regex: re.Pattern = re.compile(target) 125 126 old_failed_jobs = [] 127 for job in pipelines[1].jobs.list(all=True): 128 if ( 129 job.status != "failed" 130 or target_jobs_regex 131 and not target_jobs_regex.fullmatch(job.name) 132 ): 133 continue 134 old_failed_jobs.append(job) 135 136 job_failed = False 137 for job in pipelines[0].jobs.list(all=True): 138 if ( 139 job.status != "failed" 140 or target_jobs_regex 141 and not target_jobs_regex.fullmatch(job.name) 142 ): 143 continue 144 145 job_failed = True 146 147 previously_failed_job = job_failed_before(old_failed_jobs, job) 148 if previously_failed_job: 149 print( 150 Fore.YELLOW 151 + f":: \u001b]8;;{job.web_url}\u001b\\{job.name}\u001b]8;;\u001b\\" 152 + Fore.MAGENTA 153 + f" \u001b]8;;{previously_failed_job.web_url}\u001b\\(previous run)\u001b]8;;\u001b\\" 154 + Style.RESET_ALL 155 ) 156 else: 157 print( 158 Fore.RED 159 + f":: \u001b]8;;{job.web_url}\u001b\\{job.name}\u001b]8;;\u001b\\" 160 + Style.RESET_ALL 161 ) 162 print_failures_csv(job.id) 163 164 if not job_failed: 165 exit(0) 166 167 print("Commits between nightly pipelines:") 168 commit = project.commits.get(pipelines[0].sha) 169 while True: 170 print( 171 f"{commit.id} \u001b]8;;{commit.web_url}\u001b\\{commit.title}\u001b]8;;\u001b\\" 172 ) 173 if commit.id == pipelines[1].sha: 174 break 175 commit = project.commits.get(commit.parent_ids[0]) 176