Skip to content

Commit 4ad9b84

Browse files
committed
[DEVOPS-3294] DB unit test scripts: Add spark planned tests report
Summary: This is to enable visibility of tests that were not run because a spark job was killed or spark was just unsuccessful in running a test. run_tests_on_spark.py - produce list of planned tests. analyze_test_results.py - consider planned tests as the "total" instead of the union of spark and junit results. Added couple more output files of analysis that can be archived to jenkins. Some small enhancements to enable debugging these scripts on dev-server. Test Plan: spark-submit ... aggregate_test_reports.py ... analyze_test_results.py ... Mimicing how they are called in build-support/jenkins/yb-jenkins-test.sh, using test list c++, java, and non-existant tests. Reviewers: jharveysmith Reviewed By: jharveysmith Subscribers: devops Differential Revision: https://phorge.dev.yugabyte.com/D40493
1 parent e4e2afa commit 4ad9b84

File tree

6 files changed

+469
-23
lines changed

6 files changed

+469
-23
lines changed

build-support/jenkins/yb-jenkins-test.sh

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,17 +267,21 @@ log "Aggregating test reports"
267267
log "Analyzing test results"
268268
test_results_from_junit_xml_path=${YB_SRC_ROOT}/test_results.json
269269
test_results_from_spark_path=${BUILD_ROOT}/full_build_report.json.gz
270+
planned_tests_path=${BUILD_ROOT}/planned_tests.json
270271

271272
if [[ -f $test_results_from_junit_xml_path &&
272-
-f $test_results_from_spark_path ]]; then
273+
-f $test_results_from_spark_path &&
274+
$NUM_REPETITIONS == 1 ]]; then
273275
(
274276
set -x
275277
"$YB_SCRIPT_PATH_ANALYZE_TEST_RESULTS" \
276278
"--aggregated-json-test-results=$test_results_from_junit_xml_path" \
279+
"--planned-tests=$planned_tests_path" \
277280
"--run-tests-on-spark-report=$test_results_from_spark_path" \
278281
"--archive-dir=$YB_SRC_ROOT" \
279-
"--successful-tests-out-path=$YB_SRC_ROOT/successful_tests.txt" \
280-
"--test-list-out-path=$YB_SRC_ROOT/test_list.txt"
282+
"--successful-tests-out-path=$YB_SRC_ROOT/test_successes.txt" \
283+
"--test-list-out-path=$YB_SRC_ROOT/test_list.txt" \
284+
"--analysis-out-path=$YB_SRC_ROOT/test_analysis.txt"
281285
)
282286
else
283287
if [[ ! -f $test_results_from_junit_xml_path ]]; then
@@ -286,6 +290,9 @@ else
286290
if [[ ! -f $test_results_from_spark_path ]]; then
287291
log "File $test_results_from_spark_path does not exist"
288292
fi
293+
if [[ $NUM_REPETITIONS != 1 ]]; then
294+
log "Analyze script cannot handle multiple repetitions."
295+
fi
289296
log "Not running $YB_SCRIPT_PATH_ANALYZE_TEST_RESULTS"
290297
fi
291298

python/yugabyte/analyze_test_results.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ def parse_args() -> argparse.Namespace:
3333
help='Aggregated JSON report of test results generated by aggregate_test_reports.py. '
3434
'Usually named test_results.json.',
3535
required=True)
36+
parser.add_argument(
37+
'--planned-tests',
38+
help='Spark planned test list produced by run_tests_on_spark.py. Usually named '
39+
'planned_tests.json.',
40+
required=True)
3641
parser.add_argument(
3742
'--run-tests-on-spark-report',
3843
help='Full build report produced by run_tests_on_spark.py. Usually named '
@@ -51,6 +56,9 @@ def parse_args() -> argparse.Namespace:
5156
parser.add_argument(
5257
'--test-list-out-path',
5358
help='Write the list of test descriptors from both types of reports to this file.')
59+
parser.add_argument(
60+
'--analysis-out-path',
61+
help='Write the analysis to this file as well as stdout.')
5462
parser.add_argument(
5563
'--verbose',
5664
action='store_true',
@@ -66,8 +74,10 @@ def parse_args() -> argparse.Namespace:
6674

6775
@dataclass
6876
class AnalysisResult:
77+
num_tests_planned: int = 0
6978
num_tests_in_junit_xml: int = 0
7079
num_tests_in_spark: int = 0
80+
num_tests_did_not_run: int = 0
7181

7282
num_failed_tests_in_junit_xml: int = 0
7383
num_failed_tests_in_spark: int = 0
@@ -86,7 +96,7 @@ class AnalysisResult:
8696
num_tests_without_spark_report: int = 0
8797

8898
# Total number of unique test descriptors found across any types of reports (union).
89-
num_total_unique_tests: int = 0
99+
num_unique_test_results: int = 0
90100

91101
# Total number of unique test descriptors found across both types of reports (intersection).
92102
num_unique_tests_present_in_both_report_types: int = 0
@@ -108,6 +118,7 @@ class SingleBuildAnalyzer:
108118

109119
def __init__(self,
110120
aggregated_test_results_path: str,
121+
planned_tests_path: str,
111122
run_tests_on_spark_report_path: str,
112123
archive_dir: Optional[str],
113124
successful_tests_out_path: Optional[str] = None,
@@ -117,16 +128,20 @@ def __init__(self,
117128
results. Look at aggregate_test_reports.py for the format of this dictionary, and at
118129
python/yugabyte/test_data/clang16_debug_centos7_test_report_from_junit_xml.json.gz for
119130
an example.
131+
:param planned_tests_path: Path to the JSON file containing list of tests to be run.
132+
Example: python/yugabyte/test_data/planned_tests.json
120133
:param run_tests_on_spark_report_path: Path to the JSON file containing the full build
121134
report produced by run_tests_on_spark.py. As an example, look at the following file:
122135
python/yugabyte/test_data/clang16_debug_centos7_run_tests_on_spark_full_report.json.gz
123136
"""
124137
logging.info("Reading aggregated JUnit XML test results from %s",
125138
aggregated_test_results_path)
126-
logging.info("Reading full Spark build report from %s", run_tests_on_spark_report_path)
127139
self.test_reports_from_junit_xml = cast(
128140
List[Dict[str, Any]],
129141
json_util.read_json_file(aggregated_test_results_path)['tests'])
142+
logging.info("Reading planned Spark test list from %s", planned_tests_path)
143+
self.planned_tests_list = json_util.read_json_file(planned_tests_path)
144+
logging.info("Reading full Spark build report from %s", run_tests_on_spark_report_path)
130145
self.run_tests_on_spark_report = cast(
131146
Dict[str, SparkTaskReport],
132147
json_util.read_json_file(run_tests_on_spark_report_path)['tests'])
@@ -234,6 +249,10 @@ def analyze(self) -> AnalysisResult:
234249

235250
desc_to_spark_task_report: Dict[SimpleTestDescriptor, SparkTaskReport] = {}
236251

252+
# TODO: This script does not support multiple test repotitions. Test descriptors are
253+
# assumed to be SimpleTestDescriptors with no :::attempt_X suffixes, and
254+
# assertions ensure that the name is not unique in the input report.
255+
# Even if it is not supported, it should gracefully ignore such tests.
237256
failed_tests_in_spark: Set[SimpleTestDescriptor] = set()
238257
for test_desc_str, spark_test_report in self.run_tests_on_spark_report.items():
239258
test_desc = SimpleTestDescriptor.parse(test_desc_str)
@@ -336,19 +355,24 @@ def analyze(self) -> AnalysisResult:
336355
junit_xml_report.get('num_failures', 0) > 0):
337356
failed_tests_in_junit_xml.add(test_desc)
338357

339-
# Compare the set of tests (both successes and failures) for two types of reports.
340-
for test_desc in sorted(
341-
desc_to_spark_task_report.keys() | deduped_junit_reports_dict.keys()):
342-
reports_from_junit_xml = deduped_junit_reports_dict.get(test_desc)
358+
# Compare the spark planned tests to spark & junit results.
359+
result.num_tests_planned = len(self.planned_tests_list)
360+
planned_desc_list = [SimpleTestDescriptor.parse(td_str)
361+
for td_str in self.planned_tests_list]
362+
for test_desc in planned_desc_list:
343363
spark_task_report = desc_to_spark_task_report.get(test_desc)
344-
if reports_from_junit_xml is None:
364+
reports_from_junit_xml = deduped_junit_reports_dict.get(test_desc)
365+
if spark_task_report is None and reports_from_junit_xml is None:
366+
logging.info("Test descriptor %s has no results", test_desc)
367+
result.num_tests_did_not_run += 1
368+
result.num_tests_without_junit_xml_report += 1
369+
result.num_tests_without_spark_report += 1
370+
elif reports_from_junit_xml is None:
345371
logging.info("Test descriptor %s has no reports from JUnit XML files", test_desc)
346372
result.num_tests_without_junit_xml_report += 1
347-
continue
348-
if spark_task_report is None:
373+
elif spark_task_report is None:
349374
logging.info("Test descriptor %s has no report from Spark", test_desc)
350-
result.num_tests_without_spark_report = 1
351-
continue
375+
result.num_tests_without_spark_report += 1
352376

353377
for test_desc in sorted(failed_tests_in_spark):
354378
if test_desc not in failed_tests_in_junit_xml:
@@ -380,8 +404,8 @@ def analyze(self) -> AnalysisResult:
380404

381405
all_test_descs = (set(desc_to_spark_task_report.keys()) |
382406
set(desc_to_test_reports_from_junit_xml.keys()))
383-
result.num_total_unique_tests = len(all_test_descs)
384-
logging.info("Found %d unique tests total" % result.num_total_unique_tests)
407+
result.num_unique_test_results = len(all_test_descs)
408+
logging.info("Found %d unique tests total" % result.num_unique_test_results)
385409

386410
tests_present_in_both = (set(desc_to_spark_task_report.keys()) &
387411
set(desc_to_test_reports_from_junit_xml.keys()))
@@ -395,7 +419,7 @@ def analyze(self) -> AnalysisResult:
395419
test_descriptor.write_test_descriptors_to_file(
396420
self.successful_tests_out_path, successful_tests, 'successful tests')
397421
test_descriptor.write_test_descriptors_to_file(
398-
self.test_list_out_path, all_test_descs, 'all tests')
422+
self.test_list_out_path, planned_desc_list, 'all tests')
399423

400424
return result
401425

@@ -405,14 +429,21 @@ def main() -> None:
405429
common_util.init_logging(verbose=args.verbose)
406430
result = SingleBuildAnalyzer(
407431
args.aggregated_json_test_results,
432+
args.planned_tests,
408433
args.run_tests_on_spark_report,
409434
args.archive_dir,
410435
args.successful_tests_out_path,
411436
args.test_list_out_path
412437
).analyze()
413438

439+
stats = ''
414440
for field in dataclasses.fields(result):
415441
logging.info("%s: %s", field.name, getattr(result, field.name))
442+
stats += f"{field.name}: {getattr(result, field.name)}\n"
443+
444+
if args.analysis_out_path:
445+
logging.info("Writing the analysis stats to %s", args.analysis_out_path)
446+
file_util.write_file(stats, args.analysis_out_path)
416447

417448

418449
if __name__ == '__main__':

python/yugabyte/artifact_upload.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,11 @@ def ensure_dir_exists(dir_path: str) -> None:
258258
try:
259259
if method == UploadMethod.SSH:
260260
assert dest_host is not None
261-
subprocess.check_call(['ssh', dest_host, 'mkdir', '-p', dest_dir])
262-
subprocess.check_call(['scp', artifact_path, f'{dest_host}:{dest_dir}/'])
261+
subprocess.check_call(['ssh', '-o', 'StrictHostKeyChecking=no', dest_host,
262+
'mkdir', '-p', dest_dir])
263+
subprocess.check_call(['scp', '-o', 'StrictHostKeyChecking=no', artifact_path,
264+
f'{dest_host}:{dest_dir}/'])
265+
263266
elif method == UploadMethod.CP:
264267
ensure_dir_exists(dest_dir)
265268
subprocess.check_call(['cp', '-f', artifact_path, dest_path])

python/yugabyte/run_tests_on_spark.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,10 @@ def main() -> None:
12501250
# End of argument validation.
12511251
# ---------------------------------------------------------------------------------------------
12521252

1253-
os.environ['YB_BUILD_HOST'] = socket.gethostname()
1253+
if os.getenv('YB_SPARK_COPY_MODE') == 'SSH':
1254+
os.environ['YB_BUILD_HOST'] = os.environ['USER'] + '@' + socket.gethostname()
1255+
else:
1256+
os.environ['YB_BUILD_HOST'] = socket.gethostname()
12541257
thirdparty_path = build_paths.BuildPaths(args.build_root).thirdparty_path
12551258
assert thirdparty_path is not None
12561259
os.environ['YB_THIRDPARTY_DIR'] = thirdparty_path
@@ -1320,6 +1323,14 @@ def main() -> None:
13201323
for i in range(1, num_repetitions + 1)
13211324
]
13221325

1326+
if args.save_report_to_build_dir:
1327+
planned_report_paths = []
1328+
planned_report_paths.append(os.path.join(global_conf.build_root, 'planned_tests.json'))
1329+
planned = []
1330+
for td in test_descriptors:
1331+
planned.append(td.descriptor_str)
1332+
save_json_to_paths('planned tests', planned, planned_report_paths, should_gzip=False)
1333+
13231334
app_name_details = ['{} tests total'.format(total_num_tests)]
13241335
if num_repetitions > 1:
13251336
app_name_details += ['{} repetitions of {} tests'.format(num_repetitions, num_tests)]

python/yugabyte/test_analyze_test_results.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ def test_analyze_test_results() -> None:
2222
test_data_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_data')
2323
aggregated_test_report_path = os.path.join(
2424
test_data_base_dir, 'clang16_debug_centos7_test_report_from_junit_xml.json')
25+
planned_tests = os.path.join(
26+
test_data_base_dir, 'planned_tests.json')
2527
run_tests_on_spark_full_report = os.path.join(
2628
test_data_base_dir,
2729
'clang16_debug_centos7_run_tests_on_spark_full_report.json')
2830
analyzer = analyze_test_results.SingleBuildAnalyzer(
2931
aggregated_test_report_path,
32+
planned_tests,
3033
run_tests_on_spark_full_report,
3134
archive_dir=None)
3235
result = analyzer.analyze()
@@ -35,15 +38,17 @@ def test_analyze_test_results() -> None:
3538
analyze_test_results.AnalysisResult(
3639
num_tests_in_junit_xml=384,
3740
num_tests_in_spark=385,
41+
num_tests_planned=387,
42+
num_tests_did_not_run=2,
3843
num_failed_tests_in_junit_xml=2,
3944
num_failed_tests_in_spark=2,
4045
num_unique_failed_tests=3,
4146
num_dedup_errors_in_junit_xml=0,
42-
num_total_unique_tests=387,
47+
num_unique_test_results=387,
4348
num_tests_failed_in_spark_but_not_junit_xml=1,
4449
num_tests_failed_in_junit_xml_but_not_spark=1,
45-
num_tests_without_junit_xml_report=3,
46-
num_tests_without_spark_report=1,
50+
num_tests_without_junit_xml_report=5,
51+
num_tests_without_spark_report=2,
4752
num_successful_tests=379,
4853
num_unique_tests_present_in_both_report_types=382
4954
))

0 commit comments

Comments
 (0)