1bf215546Sopenharmony_ci#!/usr/bin/env python3 2bf215546Sopenharmony_ci# 3bf215546Sopenharmony_ci# Copyright © 2021 Google LLC 4bf215546Sopenharmony_ci# 5bf215546Sopenharmony_ci# Permission is hereby granted, free of charge, to any person obtaining a 6bf215546Sopenharmony_ci# copy of this software and associated documentation files (the "Software"), 7bf215546Sopenharmony_ci# to deal in the Software without restriction, including without limitation 8bf215546Sopenharmony_ci# the rights to use, copy, modify, merge, publish, distribute, sublicense, 9bf215546Sopenharmony_ci# and/or sell copies of the Software, and to permit persons to whom the 10bf215546Sopenharmony_ci# Software is furnished to do so, subject to the following conditions: 11bf215546Sopenharmony_ci# 12bf215546Sopenharmony_ci# The above copyright notice and this permission notice (including the next 13bf215546Sopenharmony_ci# paragraph) shall be included in all copies or substantial portions of the 14bf215546Sopenharmony_ci# Software. 15bf215546Sopenharmony_ci# 16bf215546Sopenharmony_ci# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17bf215546Sopenharmony_ci# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18bf215546Sopenharmony_ci# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19bf215546Sopenharmony_ci# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20bf215546Sopenharmony_ci# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21bf215546Sopenharmony_ci# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22bf215546Sopenharmony_ci# IN THE SOFTWARE. 23bf215546Sopenharmony_ci 24bf215546Sopenharmony_ciimport argparse 25bf215546Sopenharmony_ciimport io 26bf215546Sopenharmony_ciimport re 27bf215546Sopenharmony_ciimport socket 28bf215546Sopenharmony_ciimport time 29bf215546Sopenharmony_ci 30bf215546Sopenharmony_ci 31bf215546Sopenharmony_ciclass Connection: 32bf215546Sopenharmony_ci def __init__(self, host, port, verbose): 33bf215546Sopenharmony_ci self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 34bf215546Sopenharmony_ci self.s.connect((host, port)) 35bf215546Sopenharmony_ci self.s.setblocking(0) 36bf215546Sopenharmony_ci self.verbose = verbose 37bf215546Sopenharmony_ci 38bf215546Sopenharmony_ci def send_line(self, line): 39bf215546Sopenharmony_ci if self.verbose: 40bf215546Sopenharmony_ci print(f"IRC: sending {line}") 41bf215546Sopenharmony_ci self.s.sendall((line + '\n').encode()) 42bf215546Sopenharmony_ci 43bf215546Sopenharmony_ci def wait(self, secs): 44bf215546Sopenharmony_ci for i in range(secs): 45bf215546Sopenharmony_ci if self.verbose: 46bf215546Sopenharmony_ci while True: 47bf215546Sopenharmony_ci try: 48bf215546Sopenharmony_ci data = self.s.recv(1024) 49bf215546Sopenharmony_ci except io.BlockingIOError: 50bf215546Sopenharmony_ci break 51bf215546Sopenharmony_ci if data == "": 52bf215546Sopenharmony_ci break 53bf215546Sopenharmony_ci for line in data.decode().split('\n'): 54bf215546Sopenharmony_ci print(f"IRC: received {line}") 55bf215546Sopenharmony_ci time.sleep(1) 56bf215546Sopenharmony_ci 57bf215546Sopenharmony_ci def quit(self): 58bf215546Sopenharmony_ci self.send_line("QUIT") 59bf215546Sopenharmony_ci self.s.shutdown(socket.SHUT_WR) 60bf215546Sopenharmony_ci self.s.close() 61bf215546Sopenharmony_ci 62bf215546Sopenharmony_ci 63bf215546Sopenharmony_cidef read_flakes(results): 64bf215546Sopenharmony_ci flakes = [] 65bf215546Sopenharmony_ci csv = re.compile("(.*),(.*),(.*)") 66bf215546Sopenharmony_ci for line in open(results, 'r').readlines(): 67bf215546Sopenharmony_ci match = csv.match(line) 68bf215546Sopenharmony_ci if match.group(2) == "Flake": 69bf215546Sopenharmony_ci flakes.append(match.group(1)) 70bf215546Sopenharmony_ci return flakes 71bf215546Sopenharmony_ci 72bf215546Sopenharmony_cidef main(): 73bf215546Sopenharmony_ci parser = argparse.ArgumentParser() 74bf215546Sopenharmony_ci parser.add_argument('--host', type=str, 75bf215546Sopenharmony_ci help='IRC server hostname', required=True) 76bf215546Sopenharmony_ci parser.add_argument('--port', type=int, 77bf215546Sopenharmony_ci help='IRC server port', required=True) 78bf215546Sopenharmony_ci parser.add_argument('--results', type=str, 79bf215546Sopenharmony_ci help='results.csv file from deqp-runner or piglit-runner', required=True) 80bf215546Sopenharmony_ci parser.add_argument('--known-flakes', type=str, 81bf215546Sopenharmony_ci help='*-flakes.txt file passed to deqp-runner or piglit-runner', required=True) 82bf215546Sopenharmony_ci parser.add_argument('--channel', type=str, 83bf215546Sopenharmony_ci help='Known flakes report channel', required=True) 84bf215546Sopenharmony_ci parser.add_argument('--url', type=str, 85bf215546Sopenharmony_ci help='$CI_JOB_URL', required=True) 86bf215546Sopenharmony_ci parser.add_argument('--runner', type=str, 87bf215546Sopenharmony_ci help='$CI_RUNNER_DESCRIPTION', required=True) 88bf215546Sopenharmony_ci parser.add_argument('--branch', type=str, 89bf215546Sopenharmony_ci help='optional branch name') 90bf215546Sopenharmony_ci parser.add_argument('--branch-title', type=str, 91bf215546Sopenharmony_ci help='optional branch title') 92bf215546Sopenharmony_ci parser.add_argument('--job', type=str, 93bf215546Sopenharmony_ci help='$CI_JOB_ID', required=True) 94bf215546Sopenharmony_ci parser.add_argument('--verbose', "-v", action="store_true", 95bf215546Sopenharmony_ci help='log IRC interactions') 96bf215546Sopenharmony_ci args = parser.parse_args() 97bf215546Sopenharmony_ci 98bf215546Sopenharmony_ci flakes = read_flakes(args.results) 99bf215546Sopenharmony_ci if not flakes: 100bf215546Sopenharmony_ci exit(0) 101bf215546Sopenharmony_ci 102bf215546Sopenharmony_ci known_flakes = [] 103bf215546Sopenharmony_ci for line in open(args.known_flakes).readlines(): 104bf215546Sopenharmony_ci line = line.strip() 105bf215546Sopenharmony_ci if not line or line.startswith("#"): 106bf215546Sopenharmony_ci continue 107bf215546Sopenharmony_ci known_flakes.append(re.compile(line)) 108bf215546Sopenharmony_ci 109bf215546Sopenharmony_ci irc = Connection(args.host, args.port, args.verbose) 110bf215546Sopenharmony_ci 111bf215546Sopenharmony_ci # The nick needs to be something unique so that multiple runners 112bf215546Sopenharmony_ci # connecting at the same time don't race for one nick and get blocked. 113bf215546Sopenharmony_ci # freenode has a 16-char limit on nicks (9 is the IETF standard, but 114bf215546Sopenharmony_ci # various servers extend that). So, trim off the common prefixes of the 115bf215546Sopenharmony_ci # runner name, and append the job ID so that software runners with more 116bf215546Sopenharmony_ci # than one concurrent job (think swrast) don't collide. For freedreno, 117bf215546Sopenharmony_ci # that gives us a nick as long as db410c-N-JJJJJJJJ, and it'll be a while 118bf215546Sopenharmony_ci # before we make it to 9-digit jobs (we're at 7 so far). 119bf215546Sopenharmony_ci nick = args.runner 120bf215546Sopenharmony_ci nick = nick.replace('mesa-', '') 121bf215546Sopenharmony_ci nick = nick.replace('google-freedreno-', '') 122bf215546Sopenharmony_ci nick += f'-{args.job}' 123bf215546Sopenharmony_ci irc.send_line(f"NICK {nick}") 124bf215546Sopenharmony_ci irc.send_line(f"USER {nick} unused unused: Gitlab CI Notifier") 125bf215546Sopenharmony_ci irc.wait(10) 126bf215546Sopenharmony_ci irc.send_line(f"JOIN {args.channel}") 127bf215546Sopenharmony_ci irc.wait(1) 128bf215546Sopenharmony_ci 129bf215546Sopenharmony_ci branchinfo = "" 130bf215546Sopenharmony_ci if args.branch: 131bf215546Sopenharmony_ci branchinfo = f" on branch {args.branch} ({args.branch_title})" 132bf215546Sopenharmony_ci irc.send_line( 133bf215546Sopenharmony_ci f"PRIVMSG {args.channel} :Flakes detected in job {args.url} on {args.runner}{branchinfo}:") 134bf215546Sopenharmony_ci 135bf215546Sopenharmony_ci for flake in flakes: 136bf215546Sopenharmony_ci status = "NEW " 137bf215546Sopenharmony_ci for known in known_flakes: 138bf215546Sopenharmony_ci if known.match(flake): 139bf215546Sopenharmony_ci status = "" 140bf215546Sopenharmony_ci break 141bf215546Sopenharmony_ci 142bf215546Sopenharmony_ci irc.send_line(f"PRIVMSG {args.channel} :{status}{flake}") 143bf215546Sopenharmony_ci 144bf215546Sopenharmony_ci irc.send_line( 145bf215546Sopenharmony_ci f"PRIVMSG {args.channel} :See {args.url}/artifacts/browse/results/") 146bf215546Sopenharmony_ci 147bf215546Sopenharmony_ci irc.quit() 148bf215546Sopenharmony_ci 149bf215546Sopenharmony_ci 150bf215546Sopenharmony_ciif __name__ == '__main__': 151bf215546Sopenharmony_ci main() 152