aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikita Kozlovskiy <nikitka@gmail.com>2023-08-16 14:39:21 +0300
committernkozlovskiy <nmk@ydb.tech>2023-08-16 16:15:08 +0300
commit193c3861dc3bed68a1d0a394effdda630d0eb551 (patch)
treebe90a52ef8b2b6eae122641e3c6b5d32748afca9
parent8662d99f68311ede697154b9fb02e3fd9e9ad52e (diff)
downloadydb-193c3861dc3bed68a1d0a394effdda630d0eb551.tar.gz
CI: group tests by test shard name
CI: group tests by test shard name Pull Request resolved: #332
-rw-r--r--.github/actions/test/action.yml11
-rw-r--r--.github/config/muted_functest.txt30
-rw-r--r--.github/config/muted_shard.txt4
-rw-r--r--.github/config/muted_test.txt57
-rwxr-xr-x.github/scripts/tests/attach-logs.py11
-rwxr-xr-x.github/scripts/tests/ctest-postprocess.py30
-rwxr-xr-x.github/scripts/tests/junit-postprocess.py86
-rw-r--r--.github/scripts/tests/mute_utils.py83
-rwxr-xr-x.github/scripts/tests/pytest-postprocess.py75
-rw-r--r--.mapping.json1
10 files changed, 227 insertions, 161 deletions
diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml
index 8637015b83..1a7059a2ab 100644
--- a/.github/actions/test/action.yml
+++ b/.github/actions/test/action.yml
@@ -55,7 +55,7 @@ runs:
echo "testfilterfile=$(pwd)/.github/config/muted_test.txt" >> $GITHUB_OUTPUT
echo "testshardfilterfile=$(pwd)/.github/config/muted_shard.txt" >> $GITHUB_OUTPUT
echo "functestfilterfile=$(pwd)/.github/config/muted_functest.txt" >> $GITHUB_OUTPUT
- echo "pytest-logfilename=${{inputs.log_suffix}}-pytest-stdout.gz" >> $GITHUB_OUTPUT
+ echo "pytest-logfilename=${{inputs.log_suffix}}-pytest-stdout.log" >> $GITHUB_OUTPUT
- name: configure s3cmd
shell: bash
@@ -169,8 +169,6 @@ runs:
.github/scripts/tests/attach-logs.py \
--url-prefix $S3_URL_PREFIX/logs/ \
- --filter-shard-file ${{steps.init.outputs.testshardfilterfile}} \
- --filter-test-file ${{steps.init.outputs.testfilterfile}} \
--ctest-report $TESTREPDIR/suites/ctest_report.xml \
--junit-reports-path $TESTREPDIR/unittests/ \
--decompress \
@@ -250,7 +248,7 @@ runs:
--source-root $source_root \
--build-root $build_root \
--output-dir $TMPDIR/pytest/ \
- . | tee $WORKDIR/pytest-short.log
+ . | tee $ARTIFACTS_DIR/${{steps.init.outputs.pytest-logfilename}}
# --artifacts-dir $ARTIFACTS_DIR/pytest/ \
# --artifacts-url $S3_URL_PREFIX/pytest/ \
@@ -261,9 +259,8 @@ runs:
run: |
echo "::group::junit-postprocess"
- .github/scripts/tests/junit-postprocess.py \
+ .github/scripts/tests/pytest-postprocess.py \
--filter-file ${{ steps.init.outputs.functestfilterfile }} \
- --no-attach-filename \
$PYTESTREPDIR/
echo "::endgroup::"
@@ -282,7 +279,7 @@ runs:
testmo automation:run:submit-thread \
--instance "$TESTMO_URL" --run-id ${{steps.th.outputs.runid}} \
--results "$PYTESTREPDIR/*.xml" \
- -- cat $WORKDIR/pytest-short.log
+ -- cat $ARTIFACTS_DIR/${{steps.init.outputs.pytest-logfilename}}
- name: Test history run complete
if: always() && inputs.testman_token
diff --git a/.github/config/muted_functest.txt b/.github/config/muted_functest.txt
index 281e2d5aeb..dc3641b535 100644
--- a/.github/config/muted_functest.txt
+++ b/.github/config/muted_functest.txt
@@ -1,19 +1,17 @@
-::suite_tests.test_postgres
-sqs.common.test_queues_managing.TestQueuesManagingWithTenant
-sqs.common.test_queues_managing.TestQueuesManagingWithPathTestQueuesManagingWithPath
-sqs.common.test_queue_attributes_validation.TestQueueAttributesValidation
-sqs.common.test_queues_managing.TestQueuesManagingWithTenant
-sqs.common.test_queues_managing.TestQueuesManagingWithPathTestQueuesManagingWithPath
-sqs.with_quotas.test_quoting.TestSqsQuotingWithKesus
-sqs.large.test_leader_start_inflight.TestSqsMultinodeCluster
-sqs.messaging.test_fifo_messaging.TestSqsFifoMessagingWithTenant
-sqs.messaging.test_fifo_messaging.TestSqsFifoMessagingWithPath
::sqs.cloud.test_yandex_cloud_mode
+::suite_tests.test_postgres
::suite_tests.test_sql_logic
::suite_tests.test_stream_query
-tenants.test_tenants.TestTenants
-tenants.test_tenants
-tenants.test_dynamic_tenants
-tenants.test_storage_config.TestStorageConfig
-ydb_cli.test_ydb_impex.TestImpex
-suite_tests.test_stream_query.TestStreamQuery::test_sql_suite[results-window.test]
+ydb/tests/functional/sqs/common/test_queue_attributes_validation.py::TestQueueAttributesValidation::*
+ydb/tests/functional/sqs/common/test_queues_managing.py::TestQueuesManagingWithPathTestQueuesManagingWithPath::*
+ydb/tests/functional/sqs/common/test_queues_managing.py::TestQueuesManagingWithPathTestQueuesManagingWithPath::*
+ydb/tests/functional/sqs/common/test_queues_managing.py::TestQueuesManagingWithTenant::*
+ydb/tests/functional/sqs/common/test_queues_managing.py::TestQueuesManagingWithTenant::*
+ydb/tests/functional/sqs/messaging/test_fifo_messaging.py::TestSqsFifoMessagingWithTenant::*
+ydb/tests/functional/sqs/multinode/test_multinode_cluster.py::TestSqsMultinodeCluster::*
+ydb/tests/functional/sqs/with_quotas/test_quoting.py::TestSqsQuotingWithKesus::*
+ydb/tests/functional/suite_tests/test_stream_query.py::TestStreamQuery::test_sql_suite[results-window.test]
+ydb/tests/functional/tenants/test_dynamic_tenants.py::*
+ydb/tests/functional/tenants/test_storage_config.py::TestStorageConfig::*
+ydb/tests/functional/tenants/test_tenants.py::*
+ydb/tests/functional/ydb_cli/test_ydb_impex.py::TestImpex::*
diff --git a/.github/config/muted_shard.txt b/.github/config/muted_shard.txt
index 07a2b0da86..53513f0f12 100644
--- a/.github/config/muted_shard.txt
+++ b/.github/config/muted_shard.txt
@@ -1,4 +1,4 @@
+ydb-core-blobstorage-pdisk-ut_2
ydb-core-blobstorage-ut_blobstorage_9
-ydb-core-tx-schemeshard-ut_replication_reboots_0
ydb-core-blobstorage-ut_vdisk2_0
-ydb-core-blobstorage-pdisk-ut_2
+ydb-core-tx-schemeshard-ut_replication_reboots_0
diff --git a/.github/config/muted_test.txt b/.github/config/muted_test.txt
index 66c5b9c1e3..df05c5083a 100644
--- a/.github/config/muted_test.txt
+++ b/.github/config/muted_test.txt
@@ -1,28 +1,29 @@
-KqpFederatedQuery
-KqpSpillingFileTests::StartError
-KqpScanSpilling::SelfJoin
-YdbWorkloadTopic
-YdbWorkloadTransferTopicToTable
-SpaceCheckForDiskReassign::Basic
-TBsHuge
-SpaceCheckForDiskReassign
-IntermediateDirsReboots::CreateKesusWithIntermediateDirs
-TReplicationWithRebootsTests::CreateDropRecreate
-TSectorMap
-TMiniKQLAllocTest::TestDeallocated
-ConsistentIndexRead::InteractiveTx
-KqpExtTest::SecondaryIndexSelectUsingScripting
-KqpQuerySession::NoLocalAttach
-Describe::Statistics
-CountingEvents::Get_Mirror3of4
-TPDiskFIT
-KqpScanArrowInChanels::AllTypesColumns
-KqpScanArrowFormat::AllTypesColumnsCellvec
-TEST_BACKTRACE_AND_SYMBOLIZE::TEST_NO_KIKIMR
-YdbSdkSessionsPool::StressTestSync10
-TYardTest::TestLogMultipleWriteRead
-TYardTest::TestMultiYardLogMultipleWriteRead
-OperationLog::ConcurrentWrites
-TPDiskTest::TestMultipleLogSpliceNonceJump
-TCmsTest
-TSentinelTests
+ydb-apps-ydb-ut-workload-transfer-topic/YdbWorkloadTransferTopicToTable::*
+ydb-apps-ydb-ut/YdbWorkloadTopic::*
+ydb-core-blobstorage-pdisk-ut/TPDiskTest::TestMultipleLogSpliceNonceJump
+ydb-core-blobstorage-pdisk-ut/TYardTest::TestLogMultipleWriteRead
+ydb-core-blobstorage-pdisk-ut/TYardTest::TestMultiYardLogMultipleWriteRead
+ydb-core-blobstorage-ut/TSectorMap::*
+ydb-core-blobstorage-ut_blobstorage/CountingEvents::Get_Mirror3of4
+ydb-core-blobstorage-ut_blobstorage/SpaceCheckForDiskReassign::*
+ydb-core-blobstorage-ut_pdiskfit-ut/TPDiskFIT::*
+ydb-core-blobstorage-ut_vdisk/TBsHuge::*
+ydb-core-cms-ut/TCmsTest::*
+ydb-core-cms-ut_sentinel/TSentinelTests::*
+ydb-core-cms-ut_sentinel/TSentinelTests::PDiskErrorState
+ydb-core-cms-ut_sentinel/TSentinelTests::PDiskFaultyState
+ydb-core-debug_tools-ut/OperationLog::ConcurrentWrites
+ydb-core-kqp-runtime-ut/KqpSpillingFileTests::StartError
+ydb-core-kqp-ut-arrow/KqpScanArrowFormat::AllTypesColumnsCellvec
+ydb-core-kqp-ut-arrow/KqpScanArrowInChanels::AllTypesColumns
+ydb-core-kqp-ut-federated_query/KqpFederatedQuery::*
+ydb-core-kqp-ut-spilling/KqpScanSpilling::SelfJoin
+ydb-core-tx-schemeshard-ut_reboots/IntermediateDirsReboots::CreateKesusWithIntermediateDirs
+ydb-core-tx-schemeshard-ut_replication_reboots/TReplicationWithRebootsTests::CreateDropRecreate
+ydb-library-yql-minikql-ut/TMiniKQLAllocTest::TestDeallocated
+ydb-library-yql-utils-backtrace-ut/TEST_BACKTRACE_AND_SYMBOLIZE::TEST_NO_KIKIMR
+ydb-public-sdk-cpp-client-ydb_topic-ut/Describe::Statistics
+ydb-services-ydb-sdk_sessions_pool_ut/YdbSdkSessionsPool::StressTestSync10
+ydb-tests-functional-kqp-kqp_indexes/ConsistentIndexRead::InteractiveTx
+ydb-tests-functional-kqp-kqp_indexes/KqpExtTest::SecondaryIndexSelectUsingScripting
+ydb-tests-functional-kqp-kqp_query_session/KqpQuerySession::NoLocalAttach
diff --git a/.github/scripts/tests/attach-logs.py b/.github/scripts/tests/attach-logs.py
index 0f4a5bd785..7889464ff3 100755
--- a/.github/scripts/tests/attach-logs.py
+++ b/.github/scripts/tests/attach-logs.py
@@ -96,7 +96,12 @@ def attach_to_unittests(ctest_log: CTestLog, unit_path):
log_name = os.path.splitext(os.path.basename(fn))[0]
common_shard_name = fn_shard_part_re.sub("", log_name)
found_tests = all_found_tests.setdefault(common_shard_name, [])
- tree = ET.parse(fn)
+ try:
+ tree = ET.parse(fn)
+ except ET.ParseError as e:
+ print(f"Unable to parse {fn}: {e}")
+ continue
+
root = tree.getroot()
changed = False
@@ -119,7 +124,7 @@ def attach_to_unittests(ctest_log: CTestLog, unit_path):
if not extra_logs:
continue
- fn = f"_{shard}_not_found.xml"
+ fn = f"{shard}-0000.xml"
print(f"create {fn}")
testcases = [create_error_testcase(t.shard.name, t.classname, t.method, t.fn, t.url) for t in extra_logs]
@@ -131,8 +136,6 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument("--url-prefix", default="./")
parser.add_argument("--decompress", action="store_true", default=False, help="decompress ctest log")
- parser.add_argument("--filter-test-file", required=False)
- parser.add_argument("--filter-shard-file", required=False)
parser.add_argument("--ctest-report")
parser.add_argument("--junit-reports-path")
parser.add_argument("ctest_log")
diff --git a/.github/scripts/tests/ctest-postprocess.py b/.github/scripts/tests/ctest-postprocess.py
index 10eacf6767..8d3df91f6c 100755
--- a/.github/scripts/tests/ctest-postprocess.py
+++ b/.github/scripts/tests/ctest-postprocess.py
@@ -1,16 +1,27 @@
#!/usr/bin/env python3
import argparse
+import re
from typing import TextIO
import xml.etree.ElementTree as ET
from log_parser import ctest_log_parser, log_reader
-from mute_utils import mute_target, remove_failure, update_suite_info, MutedShardCheck
+from mute_utils import mute_target, remove_failure, update_suite_info, MuteTestCheck
def find_targets_to_remove(log_fp):
return {target for target, reason, _ in ctest_log_parser(log_fp) if reason == "Failed"}
+shard_suffix_re = re.compile(r"_\d+$")
+
+
+def strip_shardname(testcase):
+ name = testcase.get('classname')
+ classname = shard_suffix_re.sub('', name)
+
+ testcase.set('classname', classname)
+
+
def postprocess_ctest(log_fp: TextIO, ctest_junit_report, is_mute_shard, dry_run):
to_remove = find_targets_to_remove(log_fp)
tree = ET.parse(ctest_junit_report)
@@ -20,6 +31,8 @@ def postprocess_ctest(log_fp: TextIO, ctest_junit_report, is_mute_shard, dry_run
for testcase in root.findall("testcase"):
target = testcase.attrib["classname"]
+ strip_shardname(testcase)
+
if is_mute_shard(target):
if mute_target(testcase):
print(f"mute {target}")
@@ -31,13 +44,12 @@ def postprocess_ctest(log_fp: TextIO, ctest_junit_report, is_mute_shard, dry_run
n_remove_failures += 1
remove_failure(testcase)
- if n_remove_failures:
- update_suite_info(root, n_remove_failures, n_skipped=n_skipped)
- print(f"{'(dry-run) ' if dry_run else ''}update {ctest_junit_report}")
- if not dry_run:
- tree.write(ctest_junit_report, xml_declaration=True, encoding="UTF-8")
- else:
- print("nothing to remove")
+ update_suite_info(root, n_remove_failures, n_skipped=n_skipped)
+
+ print(f"{'(dry-run) ' if dry_run else ''}update {ctest_junit_report}")
+
+ if not dry_run:
+ tree.write(ctest_junit_report, xml_declaration=True, encoding="UTF-8")
def main():
@@ -50,7 +62,7 @@ def main():
args = parser.parse_args()
log = log_reader(args.ctest_log, args.decompress)
- is_mute_shard = MutedShardCheck(args.filter_file)
+ is_mute_shard = MuteTestCheck(args.filter_file)
postprocess_ctest(log, args.ctest_junit_report, is_mute_shard, args.dry_run)
diff --git a/.github/scripts/tests/junit-postprocess.py b/.github/scripts/tests/junit-postprocess.py
index 150cdfb09e..df8d07b0f3 100755
--- a/.github/scripts/tests/junit-postprocess.py
+++ b/.github/scripts/tests/junit-postprocess.py
@@ -1,80 +1,72 @@
#!/usr/bin/env python3
-import os
-import glob
import argparse
+import glob
+import os
+import re
import xml.etree.ElementTree as ET
-from mute_utils import mute_target, update_suite_info, MutedTestCheck
-from junit_utils import add_junit_property
+from mute_utils import MuteTestCheck, mute_target, recalc_suite_info
-def case_iterator(root):
- for case in root.findall("testcase"):
- cls, method = case.attrib["classname"], case.attrib["name"]
- yield case, cls, method
+shard_suffix_re = re.compile(r"-\d+$")
-def attach_filename(testcase, filename):
- shardname = os.path.splitext(filename)[0]
- add_junit_property(testcase, "shard", shardname)
+def update_testname(fn, testcase):
+ shardname = os.path.splitext(os.path.basename(fn))[0]
+ shardname = shard_suffix_re.sub("", shardname)
+ clsname = testcase.get("classname")
+ tstname = testcase.get("name")
+ testcase.set("classname", shardname)
-def postprocess_junit(is_mute_test, folder, no_attach_filename, dry_run):
- for fn in glob.glob(os.path.join(folder, "*.xml")):
- tree = ET.parse(fn)
- root = tree.getroot()
- total_err = total_fail = 0
+ testcase.set("name", f"{clsname}::{tstname}")
+ testcase.set("id", f"{shardname}_{clsname}_{tstname}")
+
+ return f"{shardname}/{clsname}::{tstname}"
- for suite in root.findall("testsuite"):
- fail_cnt = error_cnt = 0
- for case, cls, method in case_iterator(suite):
- if not no_attach_filename:
- attach_filename(case, os.path.basename(fn))
+def postprocess_yunit(fn, mute_check: MuteTestCheck, dry_run):
+ try:
+ tree = ET.parse(fn)
+ except ET.ParseError as e:
+ print(f"Unable to parse {fn}: {e}")
+ return
+
+ root = tree.getroot()
- if is_mute_test(cls, method):
- if mute_target(case):
- print(f"mute {cls}::{method}")
- fail_cnt += 1
- elif mute_target(case, "error"):
- print(f"mute error {cls}::{method}")
- error_cnt += 1
+ for testsuite in root.findall("testsuite"):
+ need_recalc = False
+ for testcase in testsuite.findall("testcase"):
+ new_name = update_testname(fn, testcase)
- if fail_cnt or error_cnt:
- update_suite_info(suite, n_remove_failures=fail_cnt, n_remove_errors=error_cnt,
- n_skipped=fail_cnt + error_cnt)
- total_err += error_cnt
- total_fail += fail_cnt
+ if mute_check(new_name) and mute_target(testcase):
+ print(f"mute {new_name}")
+ need_recalc = True
- if total_fail or total_err:
- update_suite_info(root, n_remove_errors=total_err, n_remove_failures=total_fail,
- n_skipped=total_err + total_fail)
+ if need_recalc:
+ recalc_suite_info(testsuite)
- print(f"{'(dry-run) ' if dry_run else ''}patch {fn}")
+ print(f"{'(dry-run) ' if dry_run else ''}save {fn}")
- if not dry_run:
- tree.write(fn, xml_declaration=True, encoding="UTF-8")
+ if not dry_run:
+ tree.write(fn, xml_declaration=True, encoding="UTF-8")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--filter-file", required=True)
- parser.add_argument("--no-attach-filename", action="store_true", default=False)
parser.add_argument("--dry-run", action="store_true", default=False)
parser.add_argument("yunit_path")
+
args = parser.parse_args()
if not os.path.isdir(args.yunit_path):
print(f"{args.yunit_path} is not a directory, exit")
raise SystemExit(-1)
- # FIXME: add gtest filter file ?
- is_mute_test = MutedTestCheck(args.filter_file)
-
- if not is_mute_test.has_rules:
- print("nothing to mute")
- return
+ mute_check = MuteTestCheck(args.filter_file)
- postprocess_junit(is_mute_test, args.yunit_path, args.no_attach_filename, args.dry_run)
+ for fn in glob.glob(os.path.join(args.yunit_path, "*.xml")):
+ postprocess_yunit(fn, mute_check, args.dry_run)
if __name__ == "__main__":
diff --git a/.github/scripts/tests/mute_utils.py b/.github/scripts/tests/mute_utils.py
index 5d31137fab..a096b407a6 100644
--- a/.github/scripts/tests/mute_utils.py
+++ b/.github/scripts/tests/mute_utils.py
@@ -1,63 +1,50 @@
import operator
+import re
import xml.etree.ElementTree as ET
from junit_utils import add_junit_property
-class MutedTestCheck:
- def __init__(self, fn=None):
- self.classes = set()
- self.methods = set()
+def pattern_to_re(pattern):
+ res = []
+ for c in pattern:
+ if c == '*':
+ res.append('.*')
+ else:
+ res.append(re.escape(c))
- if fn:
- self.populate(fn)
+ return f"(?:^{''.join(res)}$)"
- def populate(self, fn):
- with open(fn, "r") as fp:
- for line in fp:
- line = line.strip()
- if not line:
- continue
- if "::" in line:
- cls, method = line.split("::", maxsplit=1)
- self.methods.add((cls, method))
- else:
- self.classes.add(line)
-
- def __call__(self, cls, method=None):
- if cls in self.classes:
- return True
-
- if method and (cls, method) in self.methods:
- return True
-
- return False
- @property
- def has_rules(self):
- return len(self.classes) or len(self.methods)
+class MuteTestCheck:
+ def __init__(self, fn):
+ self.regexps = []
-
-class MutedShardCheck:
- def __init__(self, fn=None):
- self.muted = set()
- if fn:
- self.populate(fn)
-
- def populate(self, fn):
- with open(fn, "rt") as fp:
+ with open(fn, 'r') as fp:
for line in fp:
- target = line.strip()
- if target:
- self.muted.add(target)
-
- def __call__(self, target):
- return target in self.muted
+ line = line.strip()
+ pattern = pattern_to_re(line)
+
+ try:
+ self.regexps.append(re.compile(pattern))
+ except re.error:
+ print(f"Unable to compile regex {pattern!r}")
+ raise
+
+ def __call__(self, fullname):
+ for r in self.regexps:
+ if r.match(fullname):
+ return True
+ return False
-def mute_target(node, node_name="failure"):
- failure = node.find(node_name)
+def mute_target(node):
+ for node_name in ('failure', 'error'):
+ failure = node.find(node_name)
+ # print('failure', node_name, node, failure)
- if failure is None:
+ if failure is not None:
+ break
+ else:
return False
msg = failure.get("message")
@@ -117,7 +104,7 @@ def recalc_suite_info(suite):
for case in suite.findall("testcase"):
tests += 1
- elapsed += float(case.get("time"))
+ elapsed += float(case.get("time", 0))
if case.find("skipped"):
skipped += 1
if case.find("failure"):
diff --git a/.github/scripts/tests/pytest-postprocess.py b/.github/scripts/tests/pytest-postprocess.py
new file mode 100755
index 0000000000..9c0873691f
--- /dev/null
+++ b/.github/scripts/tests/pytest-postprocess.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+import argparse
+import glob
+import os
+import xml.etree.ElementTree as ET
+from mute_utils import MuteTestCheck, mute_target, recalc_suite_info
+from junit_utils import get_property_value
+
+
+def update_testname(testcase):
+ filename = get_property_value(testcase, "filename")
+
+ clsname = testcase.get("classname")
+ tstname = testcase.get("name")
+
+ if filename is None:
+ return f"{clsname}::{tstname}"
+
+ filename = filename.split("/")
+ test_fn = filename[-1]
+ folder = "/".join(filename[:-1])
+
+ testcase.set("classname", folder)
+
+ clsname = clsname.split(".")[-1]
+
+ test_name = f"{test_fn}::{clsname}::{tstname}"
+
+ testcase.set("name", test_name)
+ testcase.set("id", f"{folder}_{test_fn}_{clsname}_{tstname}")
+
+ return f"{folder}/{test_name}"
+
+
+def postprocess_pytest(fn, mute_check, dry_run):
+ tree = ET.parse(fn)
+ root = tree.getroot()
+
+ for testsuite in root.findall("testsuite"):
+ need_recalc = False
+ for testcase in testsuite.findall("testcase"):
+ new_name = update_testname(testcase)
+ if mute_check(new_name) and mute_target(testcase):
+ print(f"mute {new_name}")
+ need_recalc = True
+
+ if need_recalc:
+ recalc_suite_info(testsuite)
+
+ print(f"{'(dry-run) ' if dry_run else ''}save {fn}")
+
+ if not dry_run:
+ tree.write(fn, xml_declaration=True, encoding="UTF-8")
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--filter-file", required=True)
+ parser.add_argument("--dry-run", action="store_true", default=False)
+ parser.add_argument("pytest_xml_path")
+
+ args = parser.parse_args()
+
+ if not os.path.isdir(args.pytest_xml_path):
+ print(f"{args.pytest_xml_path} is not a directory, exit")
+ raise SystemExit(-1)
+
+ mute_check = MuteTestCheck(args.filter_file)
+
+ for fn in glob.glob(os.path.join(args.pytest_xml_path, "*.xml")):
+ postprocess_pytest(fn, mute_check, args.dry_run)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.mapping.json b/.mapping.json
index 2903369075..81f6d1c2e5 100644
--- a/.mapping.json
+++ b/.mapping.json
@@ -24,6 +24,7 @@
".github/scripts/tests/junit_utils.py":"ydb/github_toplevel/.github/scripts/tests/junit_utils.py",
".github/scripts/tests/log_parser.py":"ydb/github_toplevel/.github/scripts/tests/log_parser.py",
".github/scripts/tests/mute_utils.py":"ydb/github_toplevel/.github/scripts/tests/mute_utils.py",
+ ".github/scripts/tests/pytest-postprocess.py":"ydb/github_toplevel/.github/scripts/tests/pytest-postprocess.py",
".github/workflows/allowed_dirs.yml":"ydb/github_toplevel/.github/workflows/allowed_dirs.yml",
".github/workflows/build_and_test_ondemand.yml":"ydb/github_toplevel/.github/workflows/build_and_test_ondemand.yml",
".github/workflows/build_and_test_provisioned.yml":"ydb/github_toplevel/.github/workflows/build_and_test_provisioned.yml",