123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- #!/usr/bin/env python3
- import argparse
- import configparser
- import datetime
- import os
- import re
- import ydb
- import logging
- from transform_ya_junit import YaMuteCheck
- from update_mute_issues import (
- create_and_add_issue_to_project,
- generate_github_issue_title_and_body,
- get_muted_tests_from_issues,
- )
- # Configure logging
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
- dir = os.path.dirname(__file__)
- config = configparser.ConfigParser()
- config_file_path = f"{dir}/../../config/ydb_qa_db.ini"
- repo_path = f"{dir}/../../../"
- muted_ya_path = '.github/config/muted_ya.txt'
- config.read(config_file_path)
- DATABASE_ENDPOINT = config["QA_DB"]["DATABASE_ENDPOINT"]
- DATABASE_PATH = config["QA_DB"]["DATABASE_PATH"]
- def execute_query(driver):
- query_string = '''
- SELECT * from (
- SELECT data.*,
- CASE WHEN new_flaky.full_name IS NOT NULL THEN True ELSE False END AS new_flaky_today,
- CASE WHEN flaky.full_name IS NOT NULL THEN True ELSE False END AS flaky_today,
- CASE WHEN muted_stable.full_name IS NOT NULL THEN True ELSE False END AS muted_stable_today,
- CASE WHEN muted_stable_n_days.full_name IS NOT NULL THEN True ELSE False END AS muted_stable_n_days_today,
- CASE WHEN deleted.full_name IS NOT NULL THEN True ELSE False END AS deleted_today
- FROM
- (SELECT test_name, suite_folder, full_name, date_window, build_type, branch, days_ago_window, history, history_class, pass_count, mute_count, fail_count, skip_count, success_rate, summary, owner, is_muted, is_test_chunk, state, previous_state, state_change_date, days_in_state, previous_state_filtered, state_change_date_filtered, days_in_state_filtered, state_filtered
- FROM `test_results/analytics/tests_monitor_test_with_filtered_states`) as data
- left JOIN
- (SELECT full_name, build_type, branch
- FROM `test_results/analytics/tests_monitor_test_with_filtered_states`
- WHERE state = 'Flaky'
- AND days_in_state = 1
- AND date_window = CurrentUtcDate()
- )as new_flaky
- ON
- data.full_name = new_flaky.full_name
- and data.build_type = new_flaky.build_type
- and data.branch = new_flaky.branch
- LEFT JOIN
- (SELECT full_name, build_type, branch
- FROM `test_results/analytics/tests_monitor_test_with_filtered_states`
- WHERE state = 'Flaky'
- AND date_window = CurrentUtcDate()
- )as flaky
- ON
- data.full_name = flaky.full_name
- and data.build_type = flaky.build_type
- and data.branch = flaky.branch
- LEFT JOIN
- (SELECT full_name, build_type, branch
- FROM `test_results/analytics/tests_monitor_test_with_filtered_states`
- WHERE state = 'Muted Stable'
- AND date_window = CurrentUtcDate()
- )as muted_stable
- ON
- data.full_name = muted_stable.full_name
- and data.build_type = muted_stable.build_type
- and data.branch = muted_stable.branch
- LEFT JOIN
- (SELECT full_name, build_type, branch
- FROM `test_results/analytics/tests_monitor_test_with_filtered_states`
- WHERE state= 'Muted Stable'
- AND days_in_state >= 14
- AND date_window = CurrentUtcDate()
- and is_test_chunk = 0
- )as muted_stable_n_days
- ON
- data.full_name = muted_stable_n_days.full_name
- and data.build_type = muted_stable_n_days.build_type
- and data.branch = muted_stable_n_days.branch
-
- LEFT JOIN
- (SELECT full_name, build_type, branch
- FROM `test_results/analytics/tests_monitor_test_with_filtered_states`
- WHERE state = 'no_runs'
- AND days_in_state >= 14
- AND date_window = CurrentUtcDate()
- and is_test_chunk = 0
- )as deleted
- ON
- data.full_name = deleted.full_name
- and data.build_type = deleted.build_type
- and data.branch = deleted.branch
- )
- where date_window = CurrentUtcDate() and branch = 'main'
-
- '''
- query = ydb.ScanQuery(query_string, {})
- table_client = ydb.TableClient(driver, ydb.TableClientSettings())
- it = table_client.scan_query(query)
- results = []
- while True:
- try:
- result = next(it)
- results = results + result.result_set.rows
- except StopIteration:
- break
- return results
- def add_lines_to_file(file_path, lines_to_add):
- try:
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
- with open(file_path, 'w') as f:
- f.writelines(lines_to_add)
- logging.info(f"Lines added to {file_path}")
- except Exception as e:
- logging.error(f"Error adding lines to {file_path}: {e}")
- def apply_and_add_mutes(all_tests, output_path, mute_check):
- output_path = os.path.join(output_path, 'mute_update')
- all_tests = sorted(all_tests, key=lambda d: d['full_name'])
- try:
- deleted_tests = set(
- f"{test.get('suite_folder')} {test.get('test_name')}\n" for test in all_tests if test.get('deleted_today')
- )
- deleted_tests = sorted(deleted_tests)
- add_lines_to_file(os.path.join(output_path, 'deleted.txt'), deleted_tests)
- deleted_tests_debug = set(
- f"{test.get('suite_folder')} {test.get('test_name')} # owner {test.get('owner')} success_rate {test.get('success_rate')}%, state {test.get('state')} days in state {test.get('days_in_state')}\n"
- for test in all_tests
- if test.get('deleted_today')
- )
- deleted_tests_debug = sorted(deleted_tests_debug)
- add_lines_to_file(os.path.join(output_path, 'deleted_debug.txt'), deleted_tests_debug)
- muted_stable_tests = set(
- f"{test.get('suite_folder')} {test.get('test_name')}\n"
- for test in all_tests
- if test.get('muted_stable_n_days_today')
- )
- muted_stable_tests = sorted(muted_stable_tests)
- add_lines_to_file(os.path.join(output_path, 'muted_stable.txt'), muted_stable_tests)
- muted_stable_tests_debug = set(
- f"{test.get('suite_folder')} {test.get('test_name')} "
- + f"# owner {test.get('owner')} success_rate {test.get('success_rate')}%, state {test.get('state')} days in state {test.get('days_in_state')}\n"
- for test in all_tests
- if test.get('muted_stable_n_days_today')
- )
- muted_stable_tests_debug = sorted(muted_stable_tests_debug)
- add_lines_to_file(os.path.join(output_path, 'muted_stable_debug.txt'), muted_stable_tests_debug)
- # Add all flaky tests
- flaky_tests = set(
- re.sub(r'\d+/(\d+)\]', r'*/*]', f"{test.get('suite_folder')} {test.get('test_name')}\n")
- for test in all_tests
- if test.get('days_in_state') >= 1
- and test.get('flaky_today')
- and (test.get('pass_count') + test.get('fail_count')) >= 3
- and test.get('fail_count') > 2
- and test.get('fail_count')/(test.get('pass_count') + test.get('fail_count')) > 0.2 # <=80% success rate
- )
- flaky_tests = sorted(flaky_tests)
- add_lines_to_file(os.path.join(output_path, 'flaky.txt'), flaky_tests)
- flaky_tests_debug = set(
- re.sub(r'\d+/(\d+)\]', r'*/*]', f"{test.get('suite_folder')} {test.get('test_name')}")
- + f" # owner {test.get('owner')} success_rate {test.get('success_rate')}%, state {test.get('state')}, days in state {test.get('days_in_state')}, pass_count {test.get('pass_count')}, fail count {test.get('fail_count')}\n"
- for test in all_tests
- if test.get('days_in_state') >= 1
- and test.get('flaky_today')
- and (test.get('pass_count') + test.get('fail_count')) >= 3
- and test.get('fail_count') > 2
- and test.get('fail_count')/(test.get('pass_count') + test.get('fail_count')) > 0.2 # <=80% success rate
- )
- ## тесты может запускаться 1 раз в день. если за последние 7 дней набирается трешход то мьютим
- ## падения сегодня более весомы ??
- ## за 7 дней смотреть?
- #----
- ## Mute Flaky редко запускаемых тестов
- ## Разобраться почему 90 % флакающих тестов имеют только 1 падение и в статусе Flaky только 2 дня
- flaky_tests_debug = sorted(flaky_tests_debug)
- add_lines_to_file(os.path.join(output_path, 'flaky_debug.txt'), flaky_tests_debug)
- new_muted_ya_tests_debug = []
- new_muted_ya_tests = []
- new_muted_ya_tests_with_flaky = []
- new_muted_ya_tests_with_flaky_debug = []
- unmuted_tests_debug = []
- muted_ya_tests_sorted = []
- muted_ya_tests_sorted_debug = []
- deleted_tests_in_mute = []
- deleted_tests_in_mute_debug = []
- muted_before_count = 0
- unmuted_stable = 0
- unmuted_deleted = 0
- # Apply mute check and filter out already muted tests
- for test in all_tests:
- testsuite = test.get('suite_folder')
- testcase = test.get('test_name')
- success_rate = test.get('success_rate')
- days_in_state = test.get('days_in_state')
- owner = test.get('owner')
- state = test.get('state')
- test_string = f"{testsuite} {testcase}\n"
- test_string_debug = f"{testsuite} {testcase} # owner {owner} success_rate {success_rate}%, state {state} days in state {days_in_state}\n"
- test_string = re.sub(r'\d+/(\d+)\]', r'*/*]', test_string)
- if (
- testsuite and testcase and mute_check(testsuite, testcase) or test_string in flaky_tests
- ) and test_string not in new_muted_ya_tests_with_flaky:
- if test_string not in muted_stable_tests and test_string not in deleted_tests:
- new_muted_ya_tests_with_flaky.append(test_string)
- new_muted_ya_tests_with_flaky_debug.append(test_string_debug)
- if testsuite and testcase and mute_check(testsuite, testcase):
-
- if test_string not in muted_ya_tests_sorted:
- muted_ya_tests_sorted.append(test_string)
- muted_ya_tests_sorted_debug.append(test_string_debug)
- muted_before_count += 1
- if test_string not in new_muted_ya_tests:
- if test_string not in muted_stable_tests and test_string not in deleted_tests:
- new_muted_ya_tests.append(test_string)
- new_muted_ya_tests_debug.append(test_string_debug)
- if test_string in muted_stable_tests:
- unmuted_stable += 1
- if test_string in deleted_tests:
- unmuted_deleted += 1
- deleted_tests_in_mute.append(test_string)
- deleted_tests_in_mute_debug.append(test_string_debug)
- unmuted_tests_debug.append(test_string_debug)
- muted_ya_tests_sorted = sorted(muted_ya_tests_sorted)
- add_lines_to_file(os.path.join(output_path, 'muted_ya_sorted.txt'), muted_ya_tests_sorted)
- muted_ya_tests_sorted_debug = sorted(muted_ya_tests_sorted_debug)
- add_lines_to_file(os.path.join(output_path, 'muted_ya_sorted_debug.txt'), muted_ya_tests_sorted_debug)
- new_muted_ya_tests = sorted(new_muted_ya_tests)
- add_lines_to_file(os.path.join(output_path, 'new_muted_ya.txt'), new_muted_ya_tests)
- new_muted_ya_tests_debug = sorted(new_muted_ya_tests_debug)
- add_lines_to_file(os.path.join(output_path, 'new_muted_ya_debug.txt'), new_muted_ya_tests_debug)
- new_muted_ya_tests_with_flaky = sorted(new_muted_ya_tests_with_flaky)
- add_lines_to_file(os.path.join(output_path, 'new_muted_ya_with_flaky.txt'), new_muted_ya_tests_with_flaky)
- new_muted_ya_tests_with_flaky_debug = sorted(new_muted_ya_tests_with_flaky_debug)
- add_lines_to_file(
- os.path.join(output_path, 'new_muted_ya_with_flaky_debug.txt'), new_muted_ya_tests_with_flaky_debug
- )
- unmuted_tests_debug = sorted(unmuted_tests_debug)
- add_lines_to_file(os.path.join(output_path, 'unmuted_debug.txt'), unmuted_tests_debug)
- deleted_tests_in_mute = sorted(deleted_tests_in_mute)
- add_lines_to_file(os.path.join(output_path, 'deleted_tests_in_mute.txt'), deleted_tests_in_mute)
- deleted_tests_in_mute_debug = sorted(deleted_tests_in_mute_debug)
- add_lines_to_file(os.path.join(output_path, 'deleted_tests_in_mute_debug.txt'), deleted_tests_in_mute_debug)
- logging.info(f"Muted before script: {muted_before_count} tests")
- logging.info(f"Muted stable : {len(muted_stable_tests)}")
- logging.info(f"Flaky tests : {len(flaky_tests)}")
- logging.info(f"Result: Muted without deleted and stable : {len(new_muted_ya_tests)}")
- logging.info(f"Result: Muted without deleted and stable, with flaky : {len(new_muted_ya_tests_with_flaky)}")
- logging.info(f"Result: Unmuted tests : stable {unmuted_stable} and deleted {unmuted_deleted}")
- except (KeyError, TypeError) as e:
- logging.error(f"Error processing test data: {e}. Check your query results for valid keys.")
- return []
- return len(new_muted_ya_tests)
- def read_tests_from_file(file_path):
- result = []
- with open(file_path, "r") as fp:
- for line in fp:
- line = line.strip()
- try:
- testsuite, testcase = line.split(" ", maxsplit=1)
- result.append({'testsuite': testsuite, 'testcase': testcase, 'full_name': f"{testsuite}/{testcase}"})
- except ValueError:
- logging.warning(f"cant parse line: {line!r}")
- continue
- return result
- def create_mute_issues(all_tests, file_path):
- base_date = datetime.datetime(1970, 1, 1)
- tests_from_file = read_tests_from_file(file_path)
- muted_tests_in_issues = get_muted_tests_from_issues()
- prepared_tests_by_suite = {}
- for test in all_tests:
- for test_from_file in tests_from_file:
- if test['full_name'] == test_from_file['full_name']:
- if test['full_name'] in muted_tests_in_issues:
- logging.info(
- f"test {test['full_name']} already have issue, {muted_tests_in_issues[test['full_name']][0]['url']}"
- )
- else:
- key = f"{test_from_file['testsuite']}:{test['owner']}"
- if not prepared_tests_by_suite.get(key):
- prepared_tests_by_suite[key] = []
- prepared_tests_by_suite[key].append(
- {
- 'mute_string': f"{ test.get('suite_folder')} {test.get('test_name')}",
- 'test_name': test.get('test_name'),
- 'suite_folder': test.get('suite_folder'),
- 'full_name': test.get('full_name'),
- 'success_rate': test.get('success_rate'),
- 'days_in_state': test.get('days_in_state'),
- 'date_window': (base_date + datetime.timedelta(days=test.get('date_window'))).date() ,
- 'owner': test.get('owner'),
- 'state': test.get('state'),
- 'summary': test.get('summary'),
- 'fail_count': test.get('fail_count'),
- 'pass_count': test.get('pass_count'),
- 'branch': test.get('branch'),
- }
- )
- results = []
- for item in prepared_tests_by_suite:
- title, body = generate_github_issue_title_and_body(prepared_tests_by_suite[item])
- owner_value = prepared_tests_by_suite[item][0]['owner'].split('/', 1)[1] if '/' in prepared_tests_by_suite[item][0]['owner'] else prepared_tests_by_suite[item][0]['owner']
- result = create_and_add_issue_to_project(title, body, state='Muted', owner=owner_value)
- if not result:
- break
- else:
- results.append(
- f"Created issue '{title}' for {prepared_tests_by_suite[item][0]['owner']}, url {result['issue_url']}"
- )
- print("\n\n")
- print("\n".join(results))
- def mute_worker(args):
- # Simplified Connection
- if "CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS" not in os.environ:
- print("Error: Env variable CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS is missing, skipping")
- return 1
- else:
- # Do not set up 'real' variable from gh workflows because it interfere with ydb tests
- # So, set up it locally
- os.environ["YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS"] = os.environ[
- "CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS"
- ]
- mute_check = YaMuteCheck()
- mute_check.load(muted_ya_path)
- with ydb.Driver(
- endpoint=DATABASE_ENDPOINT,
- database=DATABASE_PATH,
- credentials=ydb.credentials_from_env_variables(),
- ) as driver:
- driver.wait(timeout=10, fail_fast=True)
- all_tests = execute_query(driver)
- if args.mode == 'update_muted_ya':
- output_path = args.output_folder
- os.makedirs(output_path, exist_ok=True)
- apply_and_add_mutes(all_tests, output_path, mute_check)
- elif args.mode == 'create_issues':
- file_path = args.file_path
- create_mute_issues(all_tests, file_path)
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Add tests to mutes files based on flaky_today condition")
- subparsers = parser.add_subparsers(dest='mode', help="Mode to perform")
- update_muted_ya_parser = subparsers.add_parser('update_muted_ya', help='create new muted_ya')
- update_muted_ya_parser.add_argument('--output_folder', default=repo_path, required=False, help='Output folder.')
- create_issues_parser = subparsers.add_parser(
- 'create_issues',
- help='create issues by muted_ya like files',
- )
- create_issues_parser.add_argument(
- '--file_path', default=f'{repo_path}/mute_update/flaky.txt', required=False, help='file path'
- )
- args = parser.parse_args()
- mute_worker(args)
|