Simplify running bazel with structured test results on CI (#29353)

* add bazel_report_helper.py

* simplify bazel invocations in selected builds

* introduce __main__

* update gitignore

* introduce sleep constant

* add type annotations

* use f-strings

* Revert "use f-strings"

This reverts commit f970d6a40b.
pull/29376/head
Jan Tattermusch 3 years ago committed by GitHub
parent 5850cba295
commit 07a75427bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .gitignore
  2. 26
      tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
  3. 19
      tools/internal_ci/linux/grpc_python_bazel_test_in_docker.sh
  4. 28
      tools/internal_ci/macos/grpc_run_bazel_c_cpp_tests.sh
  5. 15
      tools/internal_ci/windows/bazel_rbe.bat
  6. 258
      tools/run_tests/python_utils/bazel_report_helper.py

6
.gitignore vendored

@ -63,6 +63,7 @@ Gemfile.lock
# Temporary test reports
report.xml
*/sponge_log.xml
*/success_log_to_rename.xml
latency_trace.txt
latency_trace.*.txt
@ -122,6 +123,11 @@ bazel-*
bazel_format_virtual_environment/
tools/bazel-*
# Bazel wrapper
bazel_wrapper
bazel_wrapper.bat
bazel_wrapper.bazelrc
# Debug output
gdb.txt

@ -23,28 +23,10 @@ source tools/internal_ci/helper_scripts/prepare_build_linux_rc
# make sure bazel is available
tools/bazel version
# to get "bazel" link for kokoro build, we need to generate
# invocation UUID, set a flag for bazel to use it
# and upload "bazel_invocation_ids" file as artifact.
BAZEL_INVOCATION_ID="$(uuidgen)"
echo "${BAZEL_INVOCATION_ID}" >"${KOKORO_ARTIFACTS_DIR}/bazel_invocation_ids"
python3 tools/run_tests/python_utils/bazel_report_helper.py --report_path bazel_rbe
tools/bazel \
bazel_rbe/bazel_wrapper \
--bazelrc=tools/remote_build/linux_kokoro.bazelrc \
test \
--invocation_id="${BAZEL_INVOCATION_ID}" \
--workspace_status_command=tools/remote_build/workspace_status_kokoro.sh \
$@ \
-- //test/... || FAILED="true"
if [ "$UPLOAD_TEST_RESULTS" != "" ]
then
# Sleep to let ResultStore finish writing results before querying
sleep 60
python3 ./tools/run_tests/python_utils/upload_rbe_results.py
fi
if [ "$FAILED" != "" ]
then
exit 1
fi
"$@" \
-- //test/...

@ -14,18 +14,23 @@
# limitations under the License.
#
# Test full Bazel
#
# NOTE: No empty lines should appear in this file before igncr is set!
set -ex -o igncr || set -ex
set -ex
mkdir -p /var/local/git
git clone /var/local/jenkins/grpc /var/local/git/grpc
(cd /var/local/jenkins/grpc/ && git submodule foreach 'cd /var/local/git/grpc \
&& git submodule update --init --reference /var/local/jenkins/grpc/${name} \
${name}')
cd /var/local/git/grpc/test
cd /var/local/git/grpc
TEST_TARGETS="//src/python/... //tools/distrib/python/grpcio_tools/... //examples/python/..."
BAZEL_FLAGS="--test_output=errors"
bazel test ${BAZEL_FLAGS} ${TEST_TARGETS}
bazel test --config=python_single_threaded_unary_stream ${BAZEL_FLAGS} ${TEST_TARGETS}
bazel test --config=python_poller_engine ${BAZEL_FLAGS} ${TEST_TARGETS}
python3 tools/run_tests/python_utils/bazel_report_helper.py --report_path python_bazel_tests
python_bazel_tests/bazel_wrapper test ${BAZEL_FLAGS} ${TEST_TARGETS}
python3 tools/run_tests/python_utils/bazel_report_helper.py --report_path python_bazel_tests_single_threaded_unary_streams
python_bazel_tests_single_threaded_unary_streams/bazel_wrapper test --config=python_single_threaded_unary_stream ${BAZEL_FLAGS} ${TEST_TARGETS}
python3 tools/run_tests/python_utils/bazel_report_helper.py --report_path python_bazel_tests_poller_engine
python_bazel_tests_poller_engine/bazel_wrapper test --config=python_poller_engine ${BAZEL_FLAGS} ${TEST_TARGETS}

@ -28,14 +28,6 @@ tools/bazel version
./tools/run_tests/start_port_server.py
# to get "bazel" link for kokoro build, we need to generate
# invocation UUID, set a flag for bazel to use it
# and upload "bazel_invocation_ids" file as artifact.
# NOTE: UUID needs to be in lowercase for the result link to work
# (on mac "uuidgen" outputs uppercase UUID)
BAZEL_INVOCATION_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
echo "${BAZEL_INVOCATION_ID}" >"${KOKORO_ARTIFACTS_DIR}/bazel_invocation_ids"
# for kokoro mac workers, exact image version is store in a well-known location on disk
KOKORO_IMAGE_VERSION="$(cat /VERSION)"
@ -48,25 +40,13 @@ BAZEL_REMOTE_CACHE_ARGS=(
--remote_default_exec_properties="grpc_cache_silo_key2=${KOKORO_IMAGE_VERSION}"
)
python3 tools/run_tests/python_utils/bazel_report_helper.py --report_path bazel_c_cpp_tests
# run all C/C++ tests
tools/bazel \
bazel_c_cpp_tests/bazel_wrapper \
--bazelrc=tools/remote_build/mac.bazelrc \
test \
--invocation_id="${BAZEL_INVOCATION_ID}" \
--workspace_status_command=tools/remote_build/workspace_status_kokoro.sh \
--google_credentials="${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json" \
"${BAZEL_REMOTE_CACHE_ARGS[@]}" \
$BAZEL_FLAGS \
-- //test/... || FAILED="true"
if [ "$UPLOAD_TEST_RESULTS" != "" ]
then
# Sleep to let ResultStore finish writing results before querying
sleep 60
PYTHONHTTPSVERIFY=0 python3 ./tools/run_tests/python_utils/upload_rbe_results.py
fi
if [ "$FAILED" != "" ]
then
exit 1
fi
-- //test/...

@ -33,17 +33,6 @@ bash -c "tools/bazel --version && cp tools/bazel-*.exe /c/bazel/bazel.exe"
set PATH=C:\bazel;%PATH%
bazel --version
@rem Generate a random UUID and store in "bazel_invocation_ids" artifact file
powershell -Command "[guid]::NewGuid().ToString()" >%KOKORO_ARTIFACTS_DIR%/bazel_invocation_ids
set /p BAZEL_INVOCATION_ID=<%KOKORO_ARTIFACTS_DIR%/bazel_invocation_ids
python3 tools/run_tests/python_utils/bazel_report_helper.py --report_path bazel_rbe
bazel --bazelrc=tools/remote_build/windows.bazelrc --output_user_root=T:\_bazel_output test --invocation_id="%BAZEL_INVOCATION_ID%" %BAZEL_FLAGS% --workspace_status_command=tools/remote_build/workspace_status_kokoro.bat //test/...
set BAZEL_EXITCODE=%errorlevel%
if not "%UPLOAD_TEST_RESULTS%"=="" (
@rem Sleep to let ResultStore finish writing results before querying
sleep 60
python3 tools/run_tests/python_utils/upload_rbe_results.py || exit /b 1
)
exit /b %BAZEL_EXITCODE%
call bazel_rbe/bazel_wrapper.bat --bazelrc=tools/remote_build/windows.bazelrc --output_user_root=T:\_bazel_output test %BAZEL_FLAGS% -- //test/... || exit /b 1

@ -0,0 +1,258 @@
#!/usr/bin/env python3
# Copyright 2022 The gRPC Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helps with running bazel with extra settings to generate structured test reports in CI."""
import argparse
import os
import platform
import sys
import uuid
_ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../../..'))
os.chdir(_ROOT)
# How long to sleep before querying Resultstore API and uploading to bigquery
# (to let ResultStore finish writing results from the bazel invocation that has
# just finished).
_UPLOAD_RBE_RESULTS_DELAY_SECONDS = 60
def _platform_string():
"""Detect current platform"""
if platform.system() == 'Windows':
return 'windows'
elif platform.system()[:7] == 'MSYS_NT':
return 'windows'
elif platform.system() == 'Darwin':
return 'mac'
elif platform.system() == 'Linux':
return 'linux'
else:
return 'posix'
def _append_to_kokoro_bazel_invocations(invocation_id: str) -> None:
"""Kokoro can display "Bazel" result link on kokoro jobs if told so."""
# to get "bazel" link for kokoro build, we need to upload
# the "bazel_invocation_ids" file with bazel invocation ID as artifact.
kokoro_artifacts_dir = os.getenv('KOKORO_ARTIFACTS_DIR')
if kokoro_artifacts_dir:
# append the bazel invocation UUID to the bazel_invocation_ids file.
with open(os.path.join(kokoro_artifacts_dir, 'bazel_invocation_ids'),
'a') as f:
f.write(invocation_id + '\n')
print(
'Added invocation ID %s to kokoro "bazel_invocation_ids" artifact' %
invocation_id,
file=sys.stderr)
else:
print(
'Skipped adding invocation ID %s to kokoro "bazel_invocation_ids" artifact'
% invocation_id,
file=sys.stderr)
pass
def _generate_junit_report_string(report_suite_name: str, invocation_id: str,
success: bool) -> None:
"""Generate sponge_log.xml formatted report, that will make the bazel invocation reachable as a target in resultstore UI / sponge."""
bazel_invocation_url = 'https://source.cloud.google.com/results/invocations/%s' % invocation_id
package_name = report_suite_name
# set testcase name to invocation URL. That way, the link will be displayed in some form
# resultstore UI and sponge even in case the bazel invocation succeeds.
testcase_name = bazel_invocation_url
if success:
# unfortunately, neither resultstore UI nor sponge display the "system-err" output (or any other tags)
# on a passing test case. But at least we tried.
test_output_tag = '<system-err>PASSED. See invocation results here: %s</system-err>' % bazel_invocation_url
else:
# The failure output will be displayes in both resultstore UI and sponge when clicking on the failing testcase.
test_output_tag = '<failure message="Failure">FAILED. See bazel invocation results here: %s</failure>' % bazel_invocation_url
lines = [
'<testsuites>',
'<testsuite id="1" name="%s" package="%s">' %
(report_suite_name, package_name),
'<testcase name="%s">' % testcase_name,
test_output_tag,
'</testcase>'
'</testsuite>',
'</testsuites>',
]
return '\n'.join(lines)
def _create_bazel_wrapper(report_path: str, report_suite_name: str,
invocation_id: str, upload_results: bool) -> None:
"""Create a "bazel wrapper" script that will execute bazel with extra settings and postprocessing."""
os.makedirs(report_path, exist_ok=True)
bazel_wrapper_filename = os.path.join(report_path, 'bazel_wrapper')
bazel_wrapper_bat_filename = bazel_wrapper_filename + '.bat'
bazel_rc_filename = os.path.join(report_path, 'bazel_wrapper.bazelrc')
# put xml reports in a separate directory if requested by GRPC_TEST_REPORT_BASE_DIR
report_base_dir = os.getenv('GRPC_TEST_REPORT_BASE_DIR', None)
xml_report_path = os.path.abspath(
os.path.join(report_base_dir, report_path
) if report_base_dir else report_path)
os.makedirs(xml_report_path, exist_ok=True)
failing_report_filename = os.path.join(xml_report_path, 'sponge_log.xml')
success_report_filename = os.path.join(xml_report_path,
'success_log_to_rename.xml')
if _platform_string() == 'windows':
workspace_status_command = 'tools/remote_build/workspace_status_kokoro.bat'
else:
workspace_status_command = 'tools/remote_build/workspace_status_kokoro.sh'
# generate RC file with the bazel flags we want to use apply.
# Using an RC file solves problems with flag ordering in the wrapper.
# (e.g. some flags need to come after the build/test command)
with open(bazel_rc_filename, 'w') as f:
f.write('build --invocation_id="%s"\n' % invocation_id)
f.write('build --workspace_status_command="%s"\n' %
workspace_status_command)
# generate "failing" and "success" report
# the "failing" is named as "sponge_log.xml", which is the name picked up by sponge/resultstore
# so the failing report will be used by default (unless we later replace the report with
# one that says "success"). That way if something goes wrong before bazel is run,
# there will at least be a "failing" target that indicates that (we really don't want silent failures).
with open(failing_report_filename, 'w') as f:
f.write(
_generate_junit_report_string(report_suite_name,
invocation_id,
success=False))
with open(success_report_filename, 'w') as f:
f.write(
_generate_junit_report_string(report_suite_name,
invocation_id,
success=True))
# generate the bazel wrapper for linux/macos
with open(bazel_wrapper_filename, 'w') as f:
intro_lines = [
'#!/bin/bash',
'set -ex',
'',
'tools/bazel --bazelrc="%s" "$@" || FAILED=true' %
bazel_rc_filename,
'',
]
if upload_results:
upload_results_lines = [
'sleep %s' % _UPLOAD_RBE_RESULTS_DELAY_SECONDS,
'PYTHONHTTPSVERIFY=0 python3 ./tools/run_tests/python_utils/upload_rbe_results.py --invocation_id="%s"'
% invocation_id,
'',
]
else:
upload_results_lines = []
outro_lines = [
'if [ "$FAILED" != "" ]',
'then',
' exit 1',
'else',
' # success: plant the pre-generated xml report that says "success"',
' mv -f %s %s' %
(success_report_filename, failing_report_filename),
'fi',
]
lines = [
line + '\n'
for line in intro_lines + upload_results_lines + outro_lines
]
f.writelines(lines)
os.chmod(bazel_wrapper_filename, 0o775) # make the unix wrapper executable
# generate bazel wrapper for windows
with open(bazel_wrapper_bat_filename, 'w') as f:
intro_lines = [
'@echo on',
'',
'bazel --bazelrc="%s" %%*' % bazel_rc_filename,
'set BAZEL_EXITCODE=%errorlevel%',
'',
]
if upload_results:
upload_results_lines = [
'sleep %s' % _UPLOAD_RBE_RESULTS_DELAY_SECONDS,
'python3 tools/run_tests/python_utils/upload_rbe_results.py --invocation_id="%s" || exit /b 1'
% invocation_id,
'',
]
else:
upload_results_lines = []
outro_lines = [
'if %BAZEL_EXITCODE% == 0 (',
' @rem success: plant the pre-generated xml report that says "success"',
' mv -f %s %s' %
(success_report_filename, failing_report_filename),
')',
'exit /b %BAZEL_EXITCODE%',
]
lines = [
line + '\n'
for line in intro_lines + upload_results_lines + outro_lines
]
f.writelines(lines)
print('Bazel invocation ID: %s' % invocation_id, file=sys.stderr)
print('Upload test results to BigQuery after bazel runs: %s' %
upload_results,
file=sys.stderr)
print('Generated bazel wrapper: %s' % bazel_wrapper_filename,
file=sys.stderr)
print('Generated bazel wrapper: %s' % bazel_wrapper_bat_filename,
file=sys.stderr)
if __name__ == '__main__':
# parse command line
argp = argparse.ArgumentParser(
description=
'Generate bazel wrapper to help with bazel test reports in CI.')
argp.add_argument(
'--report_path',
required=True,
type=str,
help=
'Path under which the bazel wrapper and other files are going to be generated'
)
argp.add_argument('--report_suite_name',
default='bazel_invocations',
type=str,
help='Test suite name to use in generated XML report')
args = argp.parse_args()
# generate new bazel invocation ID
invocation_id = str(uuid.uuid4())
report_path = args.report_path
report_suite_name = args.report_suite_name
upload_results = True if os.getenv('UPLOAD_TEST_RESULTS') else False
_append_to_kokoro_bazel_invocations(invocation_id)
_create_bazel_wrapper(report_path, report_suite_name, invocation_id,
upload_results)
Loading…
Cancel
Save