[PATCH v1] drm/ci: remove update-xfails.py

Vignesh Raman posted 1 patch 3 weeks, 4 days ago
drivers/gpu/drm/ci/xfails/requirements.txt |  17 --
drivers/gpu/drm/ci/xfails/update-xfails.py | 204 ---------------------
2 files changed, 221 deletions(-)
delete mode 100644 drivers/gpu/drm/ci/xfails/requirements.txt
delete mode 100755 drivers/gpu/drm/ci/xfails/update-xfails.py
[PATCH v1] drm/ci: remove update-xfails.py
Posted by Vignesh Raman 3 weeks, 4 days ago
We can remove the xfails/update-xfails.py script as it is not
used in CI jobs. Once ci-collate [1] is tested for drm-ci,
we can use this tool directly to update fails and flakes.

[1] https://gitlab.freedesktop.org/gfx-ci/ci-collate/

Signed-off-by: Vignesh Raman <vignesh.raman@collabora.com>
---
 drivers/gpu/drm/ci/xfails/requirements.txt |  17 --
 drivers/gpu/drm/ci/xfails/update-xfails.py | 204 ---------------------
 2 files changed, 221 deletions(-)
 delete mode 100644 drivers/gpu/drm/ci/xfails/requirements.txt
 delete mode 100755 drivers/gpu/drm/ci/xfails/update-xfails.py

diff --git a/drivers/gpu/drm/ci/xfails/requirements.txt b/drivers/gpu/drm/ci/xfails/requirements.txt
deleted file mode 100644
index 5e6d48d98e4e..000000000000
--- a/drivers/gpu/drm/ci/xfails/requirements.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-git+https://gitlab.freedesktop.org/gfx-ci/ci-collate@09e7142715c16f54344ddf97013331ba063b162b
-termcolor==2.3.0
-
-# ci-collate dependencies
-certifi==2023.7.22
-charset-normalizer==3.2.0
-idna==3.4
-pip==23.3
-python-gitlab==3.15.0
-requests==2.31.0
-requests-toolbelt==1.0.0
-ruamel.yaml==0.17.32
-ruamel.yaml.clib==0.2.7
-setuptools==70.0.0
-tenacity==8.2.3
-urllib3==2.0.7
-wheel==0.41.1
diff --git a/drivers/gpu/drm/ci/xfails/update-xfails.py b/drivers/gpu/drm/ci/xfails/update-xfails.py
deleted file mode 100755
index a446e98d72a1..000000000000
--- a/drivers/gpu/drm/ci/xfails/update-xfails.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/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.")
-- 
2.43.0
Re: [PATCH v1] drm/ci: remove update-xfails.py
Posted by WangYuli 3 weeks, 3 days ago
On 2024/10/30 17:17, Vignesh Raman wrote:
> We can remove the xfails/update-xfails.py script as it is not
> used in CI jobs. Once ci-collate [1] is tested for drm-ci,
> we can use this tool directly to update fails and flakes.
>
> [1] https://gitlab.freedesktop.org/gfx-ci/ci-collate/
>
> Signed-off-by: Vignesh Raman <vignesh.raman@collabora.com>
> ---
>   drivers/gpu/drm/ci/xfails/requirements.txt |  17 --
>   drivers/gpu/drm/ci/xfails/update-xfails.py | 204 ---------------------
>   2 files changed, 221 deletions(-)
>   delete mode 100644 drivers/gpu/drm/ci/xfails/requirements.txt
>   delete mode 100755 drivers/gpu/drm/ci/xfails/update-xfails.py
>
> diff --git a/drivers/gpu/drm/ci/xfails/requirements.txt b/drivers/gpu/drm/ci/xfails/requirements.txt
> deleted file mode 100644
> index 5e6d48d98e4e..000000000000
> --- a/drivers/gpu/drm/ci/xfails/requirements.txt
> +++ /dev/null
> @@ -1,17 +0,0 @@
> -git+https://gitlab.freedesktop.org/gfx-ci/ci-collate@09e7142715c16f54344ddf97013331ba063b162b
> -termcolor==2.3.0
> -
> -# ci-collate dependencies
> -certifi==2023.7.22
> -charset-normalizer==3.2.0
> -idna==3.4
> -pip==23.3
> -python-gitlab==3.15.0
> -requests==2.31.0
> -requests-toolbelt==1.0.0
> -ruamel.yaml==0.17.32
> -ruamel.yaml.clib==0.2.7
> -setuptools==70.0.0
> -tenacity==8.2.3
> -urllib3==2.0.7
> -wheel==0.41.1
> diff --git a/drivers/gpu/drm/ci/xfails/update-xfails.py b/drivers/gpu/drm/ci/xfails/update-xfails.py
> deleted file mode 100755
> index a446e98d72a1..000000000000
> --- a/drivers/gpu/drm/ci/xfails/update-xfails.py
> +++ /dev/null
> @@ -1,204 +0,0 @@
> -#!/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.")

Reviewed-by: WangYuli <wangyuli@uniontech.com>

Cheers,
-- 
WangYuli*
*
Re: [PATCH v1] drm/ci: remove update-xfails.py
Posted by Helen Mae Koike Fornazier 2 weeks, 5 days ago



---- On Thu, 31 Oct 2024 02:39:13 -0300 WangYuli  wrote ---

 > 
 > On 2024/10/30 17:17, Vignesh Raman wrote: 
 > > We can remove the xfails/update-xfails.py script as it is not 
 > > used in CI jobs. Once ci-collate [1] is tested for drm-ci, 
 > > we can use this tool directly to update fails and flakes. 
 > > 
 > > [1] https://gitlab.freedesktop.org/gfx-ci/ci-collate/ 
 > > 
 > > Signed-off-by: Vignesh Raman vignesh.raman@collabora.com> 


Applied to drm-misc-next

Thanks
Helen

 > > --- 
 > >   drivers/gpu/drm/ci/xfails/requirements.txt |  17 -- 
 > >   drivers/gpu/drm/ci/xfails/update-xfails.py | 204 --------------------- 
 > >   2 files changed, 221 deletions(-) 
 > >   delete mode 100644 drivers/gpu/drm/ci/xfails/requirements.txt 
 > >   delete mode 100755 drivers/gpu/drm/ci/xfails/update-xfails.py 
 > > 
 > > diff --git a/drivers/gpu/drm/ci/xfails/requirements.txt b/drivers/gpu/drm/ci/xfails/requirements.txt 
 > > deleted file mode 100644 
 > > index 5e6d48d98e4e..000000000000 
 > > --- a/drivers/gpu/drm/ci/xfails/requirements.txt 
 > > +++ /dev/null 
 > > @@ -1,17 +0,0 @@ 
 > > -git+https://gitlab.freedesktop.org/gfx-ci/ci-collate@09e7142715c16f54344ddf97013331ba063b162b 
 > > -termcolor==2.3.0 
 > > - 
 > > -# ci-collate dependencies 
 > > -certifi==2023.7.22 
 > > -charset-normalizer==3.2.0 
 > > -idna==3.4 
 > > -pip==23.3 
 > > -python-gitlab==3.15.0 
 > > -requests==2.31.0 
 > > -requests-toolbelt==1.0.0 
 > > -ruamel.yaml==0.17.32 
 > > -ruamel.yaml.clib==0.2.7 
 > > -setuptools==70.0.0 
 > > -tenacity==8.2.3 
 > > -urllib3==2.0.7 
 > > -wheel==0.41.1 
 > > diff --git a/drivers/gpu/drm/ci/xfails/update-xfails.py b/drivers/gpu/drm/ci/xfails/update-xfails.py 
 > > deleted file mode 100755 
 > > index a446e98d72a1..000000000000 
 > > --- a/drivers/gpu/drm/ci/xfails/update-xfails.py 
 > > +++ /dev/null 
 > > @@ -1,204 +0,0 @@ 
 > > -#!/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.") 
 >  
 > Reviewed-by: WangYuli wangyuli@uniontech.com> 
 >  
 > Cheers, 
 > -- 
 > WangYuli* 
 > * 
 >