brintos

brintos / linux-shallow public Read only

0
0
Text · 7.3 KiB · a446e98 Raw
205 lines · python
1#!/usr/bin/env python32 3import argparse4from collections import defaultdict5import difflib6import os7import re8from glcollate import Collate9from termcolor import colored10from urllib.parse import urlparse11 12 13def get_canonical_name(job_name):14    return re.split(r" \d+/\d+", job_name)[0]15 16 17def get_xfails_file_path(job_name, suffix):18    canonical_name = get_canonical_name(job_name)19    name = canonical_name.replace(":", "-")20    script_dir = os.path.dirname(os.path.abspath(__file__))21    return os.path.join(script_dir, f"{name}-{suffix}.txt")22 23 24def get_unit_test_name_and_results(unit_test):25    if "Artifact results/failures.csv not found" in unit_test or '' == unit_test:26        return None, None27    unit_test_name, unit_test_result = unit_test.strip().split(",")28    return unit_test_name, unit_test_result29 30 31def read_file(file_path):32    try:33        with open(file_path, "r") as file:34            f = file.readlines()35            if len(f):36                f[-1] = f[-1].strip() + "\n"37            return f38    except FileNotFoundError:39        return []40 41 42def save_file(content, file_path):43    # delete file is content is empty44    if not content or not any(content):45        if os.path.exists(file_path):46            os.remove(file_path)47        return48 49    with open(file_path, "w") as file:50        file.writelines(content)51 52 53def is_test_present_on_file(file_content, unit_test_name):54    return any(unit_test_name in line for line in file_content)55 56 57def is_unit_test_present_in_other_jobs(unit_test, job_ids):58    return all(unit_test in job_ids[job_id] for job_id in job_ids)59 60 61def remove_unit_test_if_present(lines, unit_test_name):62    if not is_test_present_on_file(lines, unit_test_name):63        return64    lines[:] = [line for line in lines if unit_test_name not in line]65 66 67def add_unit_test_if_not_present(lines, unit_test_name, file_name):68    # core_getversion is mandatory69    if "core_getversion" in unit_test_name:70        print("WARNING: core_getversion should pass, not adding it to", os.path.basename(file_name))71    elif all(unit_test_name not in line for line in lines):72        lines.append(unit_test_name + "\n")73 74 75def update_unit_test_result_in_fails_txt(fails_txt, unit_test):76    unit_test_name, unit_test_result = get_unit_test_name_and_results(unit_test)77    for i, line in enumerate(fails_txt):78        if unit_test_name in line:79            _, current_result = get_unit_test_name_and_results(line)80            fails_txt[i] = unit_test + "\n"81            return82 83 84def add_unit_test_or_update_result_to_fails_if_present(fails_txt, unit_test, fails_txt_path):85    unit_test_name, _ = get_unit_test_name_and_results(unit_test)86    if not is_test_present_on_file(fails_txt, unit_test_name):87        add_unit_test_if_not_present(fails_txt, unit_test, fails_txt_path)88    # if it is present but not with the same result89    elif not is_test_present_on_file(fails_txt, unit_test):90        update_unit_test_result_in_fails_txt(fails_txt, unit_test)91 92 93def split_unit_test_from_collate(xfails):94    for job_name in xfails.keys():95        for job_id in xfails[job_name].copy().keys():96            if "not found" in xfails[job_name][job_id].content_as_str:97                del xfails[job_name][job_id]98                continue99            xfails[job_name][job_id] = xfails[job_name][job_id].content_as_str.splitlines()100 101 102def get_xfails_from_pipeline_url(pipeline_url):103    parsed_url = urlparse(pipeline_url)104    path_components = parsed_url.path.strip("/").split("/")105 106    namespace = path_components[0]107    project = path_components[1]108    pipeline_id = path_components[-1]109 110    print("Collating from:", namespace, project, pipeline_id)111    xfails = (112        Collate(namespace=namespace, project=project)113        .from_pipeline(pipeline_id)114        .get_artifact("results/failures.csv")115    )116 117    split_unit_test_from_collate(xfails)118    return xfails119 120 121def get_xfails_from_pipeline_urls(pipelines_urls):122    xfails = defaultdict(dict)123 124    for url in pipelines_urls:125        new_xfails = get_xfails_from_pipeline_url(url)126        for key in new_xfails:127            xfails[key].update(new_xfails[key])128 129    return xfails130 131 132def print_diff(old_content, new_content, file_name):133    diff = difflib.unified_diff(old_content, new_content, lineterm="", fromfile=file_name, tofile=file_name)134    diff = [colored(line, "green") if line.startswith("+") else135            colored(line, "red") if line.startswith("-") else line for line in diff]136    print("\n".join(diff[:3]))137    print("".join(diff[3:]))138 139 140def main(pipelines_urls, only_flakes):141    xfails = get_xfails_from_pipeline_urls(pipelines_urls)142 143    for job_name in xfails.keys():144        fails_txt_path = get_xfails_file_path(job_name, "fails")145        flakes_txt_path = get_xfails_file_path(job_name, "flakes")146 147        fails_txt = read_file(fails_txt_path)148        flakes_txt = read_file(flakes_txt_path)149 150        fails_txt_original = fails_txt.copy()151        flakes_txt_original = flakes_txt.copy()152 153        for job_id in xfails[job_name].keys():154            for unit_test in xfails[job_name][job_id]:155                unit_test_name, unit_test_result = get_unit_test_name_and_results(unit_test)156 157                if not unit_test_name:158                    continue159 160                if only_flakes:161                    remove_unit_test_if_present(fails_txt, unit_test_name)162                    add_unit_test_if_not_present(flakes_txt, unit_test_name, flakes_txt_path)163                    continue164 165                # drop it from flakes if it is present to analyze it again166                remove_unit_test_if_present(flakes_txt, unit_test_name)167 168                if unit_test_result == "UnexpectedPass":169                    remove_unit_test_if_present(fails_txt, unit_test_name)170                    # flake result171                    if not is_unit_test_present_in_other_jobs(unit_test, xfails[job_name]):172                        add_unit_test_if_not_present(flakes_txt, unit_test_name, flakes_txt_path)173                    continue174 175                # flake result176                if not is_unit_test_present_in_other_jobs(unit_test, xfails[job_name]):177                    remove_unit_test_if_present(fails_txt, unit_test_name)178                    add_unit_test_if_not_present(flakes_txt, unit_test_name, flakes_txt_path)179                    continue180 181                # consistent result182                add_unit_test_or_update_result_to_fails_if_present(fails_txt, unit_test,183                                                                   fails_txt_path)184 185        fails_txt.sort()186        flakes_txt.sort()187 188        if fails_txt != fails_txt_original:189            save_file(fails_txt, fails_txt_path)190            print_diff(fails_txt_original, fails_txt, os.path.basename(fails_txt_path))191        if flakes_txt != flakes_txt_original:192            save_file(flakes_txt, flakes_txt_path)193            print_diff(flakes_txt_original, flakes_txt, os.path.basename(flakes_txt_path))194 195 196if __name__ == "__main__":197    parser = argparse.ArgumentParser(description="Update xfails from a given pipeline.")198    parser.add_argument("pipeline_urls", nargs="+", type=str, help="URLs to the pipelines to analyze the failures.")199    parser.add_argument("--only-flakes", action="store_true", help="Treat every detected failure as a flake, edit *-flakes.txt only.")200 201    args = parser.parse_args()202 203    main(args.pipeline_urls, args.only_flakes)204    print("Done.")205