e2ef28ea15
Now the testlist is used from IGT build, so update xfails with the new testlist. Set the timeout of all i915 jobs to 1h30m since some jobs takes more than 1 hour to complete. Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> # msm testlists Signed-off-by: Vignesh Raman <vignesh.raman@collabora.com> Reviewed-by: Jessica Zhang <quic_jesszhan@quicinc.com> # msm test list Acked-by: Helen Koike <helen.koike@collabora.com> Signed-off-by: Helen Koike <helen.koike@collabora.com> Link: https://patchwork.freedesktop.org/patch/msgid/20240529024049.356327-7-vignesh.raman@collabora.com Signed-off-by: Helen Koike <helen.koike@collabora.com>
205 lines
7.3 KiB
Python
Executable File
205 lines
7.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
from collections import defaultdict
|
|
import difflib
|
|
import os
|
|
import re
|
|
from glcollate import Collate
|
|
from termcolor import colored
|
|
from urllib.parse import urlparse
|
|
|
|
|
|
def get_canonical_name(job_name):
|
|
return re.split(r" \d+/\d+", job_name)[0]
|
|
|
|
|
|
def get_xfails_file_path(job_name, suffix):
|
|
canonical_name = get_canonical_name(job_name)
|
|
name = canonical_name.replace(":", "-")
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
return os.path.join(script_dir, f"{name}-{suffix}.txt")
|
|
|
|
|
|
def get_unit_test_name_and_results(unit_test):
|
|
if "Artifact results/failures.csv not found" in unit_test or '' == unit_test:
|
|
return None, None
|
|
unit_test_name, unit_test_result = unit_test.strip().split(",")
|
|
return unit_test_name, unit_test_result
|
|
|
|
|
|
def read_file(file_path):
|
|
try:
|
|
with open(file_path, "r") as file:
|
|
f = file.readlines()
|
|
if len(f):
|
|
f[-1] = f[-1].strip() + "\n"
|
|
return f
|
|
except FileNotFoundError:
|
|
return []
|
|
|
|
|
|
def save_file(content, file_path):
|
|
# delete file is content is empty
|
|
if not content or not any(content):
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
return
|
|
|
|
with open(file_path, "w") as file:
|
|
file.writelines(content)
|
|
|
|
|
|
def is_test_present_on_file(file_content, unit_test_name):
|
|
return any(unit_test_name in line for line in file_content)
|
|
|
|
|
|
def is_unit_test_present_in_other_jobs(unit_test, job_ids):
|
|
return all(unit_test in job_ids[job_id] for job_id in job_ids)
|
|
|
|
|
|
def remove_unit_test_if_present(lines, unit_test_name):
|
|
if not is_test_present_on_file(lines, unit_test_name):
|
|
return
|
|
lines[:] = [line for line in lines if unit_test_name not in line]
|
|
|
|
|
|
def add_unit_test_if_not_present(lines, unit_test_name, file_name):
|
|
# core_getversion is mandatory
|
|
if "core_getversion" in unit_test_name:
|
|
print("WARNING: core_getversion should pass, not adding it to", os.path.basename(file_name))
|
|
elif all(unit_test_name not in line for line in lines):
|
|
lines.append(unit_test_name + "\n")
|
|
|
|
|
|
def update_unit_test_result_in_fails_txt(fails_txt, unit_test):
|
|
unit_test_name, unit_test_result = get_unit_test_name_and_results(unit_test)
|
|
for i, line in enumerate(fails_txt):
|
|
if unit_test_name in line:
|
|
_, current_result = get_unit_test_name_and_results(line)
|
|
fails_txt[i] = unit_test + "\n"
|
|
return
|
|
|
|
|
|
def add_unit_test_or_update_result_to_fails_if_present(fails_txt, unit_test, fails_txt_path):
|
|
unit_test_name, _ = get_unit_test_name_and_results(unit_test)
|
|
if not is_test_present_on_file(fails_txt, unit_test_name):
|
|
add_unit_test_if_not_present(fails_txt, unit_test, fails_txt_path)
|
|
# if it is present but not with the same result
|
|
elif not is_test_present_on_file(fails_txt, unit_test):
|
|
update_unit_test_result_in_fails_txt(fails_txt, unit_test)
|
|
|
|
|
|
def split_unit_test_from_collate(xfails):
|
|
for job_name in xfails.keys():
|
|
for job_id in xfails[job_name].copy().keys():
|
|
if "not found" in xfails[job_name][job_id].content_as_str:
|
|
del xfails[job_name][job_id]
|
|
continue
|
|
xfails[job_name][job_id] = xfails[job_name][job_id].content_as_str.splitlines()
|
|
|
|
|
|
def get_xfails_from_pipeline_url(pipeline_url):
|
|
parsed_url = urlparse(pipeline_url)
|
|
path_components = parsed_url.path.strip("/").split("/")
|
|
|
|
namespace = path_components[0]
|
|
project = path_components[1]
|
|
pipeline_id = path_components[-1]
|
|
|
|
print("Collating from:", namespace, project, pipeline_id)
|
|
xfails = (
|
|
Collate(namespace=namespace, project=project)
|
|
.from_pipeline(pipeline_id)
|
|
.get_artifact("results/failures.csv")
|
|
)
|
|
|
|
split_unit_test_from_collate(xfails)
|
|
return xfails
|
|
|
|
|
|
def get_xfails_from_pipeline_urls(pipelines_urls):
|
|
xfails = defaultdict(dict)
|
|
|
|
for url in pipelines_urls:
|
|
new_xfails = get_xfails_from_pipeline_url(url)
|
|
for key in new_xfails:
|
|
xfails[key].update(new_xfails[key])
|
|
|
|
return xfails
|
|
|
|
|
|
def print_diff(old_content, new_content, file_name):
|
|
diff = difflib.unified_diff(old_content, new_content, lineterm="", fromfile=file_name, tofile=file_name)
|
|
diff = [colored(line, "green") if line.startswith("+") else
|
|
colored(line, "red") if line.startswith("-") else line for line in diff]
|
|
print("\n".join(diff[:3]))
|
|
print("".join(diff[3:]))
|
|
|
|
|
|
def main(pipelines_urls, only_flakes):
|
|
xfails = get_xfails_from_pipeline_urls(pipelines_urls)
|
|
|
|
for job_name in xfails.keys():
|
|
fails_txt_path = get_xfails_file_path(job_name, "fails")
|
|
flakes_txt_path = get_xfails_file_path(job_name, "flakes")
|
|
|
|
fails_txt = read_file(fails_txt_path)
|
|
flakes_txt = read_file(flakes_txt_path)
|
|
|
|
fails_txt_original = fails_txt.copy()
|
|
flakes_txt_original = flakes_txt.copy()
|
|
|
|
for job_id in xfails[job_name].keys():
|
|
for unit_test in xfails[job_name][job_id]:
|
|
unit_test_name, unit_test_result = get_unit_test_name_and_results(unit_test)
|
|
|
|
if not unit_test_name:
|
|
continue
|
|
|
|
if only_flakes:
|
|
remove_unit_test_if_present(fails_txt, unit_test_name)
|
|
add_unit_test_if_not_present(flakes_txt, unit_test_name, flakes_txt_path)
|
|
continue
|
|
|
|
# drop it from flakes if it is present to analyze it again
|
|
remove_unit_test_if_present(flakes_txt, unit_test_name)
|
|
|
|
if unit_test_result == "UnexpectedPass":
|
|
remove_unit_test_if_present(fails_txt, unit_test_name)
|
|
# flake result
|
|
if not is_unit_test_present_in_other_jobs(unit_test, xfails[job_name]):
|
|
add_unit_test_if_not_present(flakes_txt, unit_test_name, flakes_txt_path)
|
|
continue
|
|
|
|
# flake result
|
|
if not is_unit_test_present_in_other_jobs(unit_test, xfails[job_name]):
|
|
remove_unit_test_if_present(fails_txt, unit_test_name)
|
|
add_unit_test_if_not_present(flakes_txt, unit_test_name, flakes_txt_path)
|
|
continue
|
|
|
|
# consistent result
|
|
add_unit_test_or_update_result_to_fails_if_present(fails_txt, unit_test,
|
|
fails_txt_path)
|
|
|
|
fails_txt.sort()
|
|
flakes_txt.sort()
|
|
|
|
if fails_txt != fails_txt_original:
|
|
save_file(fails_txt, fails_txt_path)
|
|
print_diff(fails_txt_original, fails_txt, os.path.basename(fails_txt_path))
|
|
if flakes_txt != flakes_txt_original:
|
|
save_file(flakes_txt, flakes_txt_path)
|
|
print_diff(flakes_txt_original, flakes_txt, os.path.basename(flakes_txt_path))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Update xfails from a given pipeline.")
|
|
parser.add_argument("pipeline_urls", nargs="+", type=str, help="URLs to the pipelines to analyze the failures.")
|
|
parser.add_argument("--only-flakes", action="store_true", help="Treat every detected failure as a flake, edit *-flakes.txt only.")
|
|
|
|
args = parser.parse_args()
|
|
|
|
main(args.pipeline_urls, args.only_flakes)
|
|
print("Done.")
|