blob: 8d8a5a577a3a0574fdde350cf426138adda2e660 [file] [log] [blame]
#!/usr/bin/env python2
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
#
# This script runs on the distributed-test slave and acts
# as a wrapper around run-test.sh.
#
# The distributed testing system can't pass in environment variables
# to commands, so this takes some parameters, turns them into environment
# variables, and then executes the test wrapper.
#
# We also 'cat' the test log upon completion so that the test logs are
# uploaded by the test slave back.
import glob
import logging
import optparse
import os
import re
import shutil
import subprocess
import sys
ME = os.path.abspath(__file__)
ROOT = os.path.abspath(os.path.join(os.path.dirname(ME), ".."))
with open(os.path.join(ROOT, "build-support", "java-home-candidates.txt"), 'r') as candidates:
JAVA_CANDIDATES = [x.strip() for x in candidates.readlines() if not x.startswith("#")]
# Ensure there aren't trailing comments in the path list.
for c in JAVA_CANDIDATES:
assert '#' not in c
def is_elf_binary(path):
""" Determine if the given path is an ELF binary (executable or shared library) """
if not os.path.isfile(path) or os.path.islink(path):
return False
try:
with open(path, "rb") as f:
magic = f.read(4)
return magic == "\x7fELF"
except:
# Ignore unreadable files
return False
def fix_rpath_component(bin_path, path):
"""
Given an RPATH component 'path' of the binary located at 'bin_path',
fix the thirdparty dir to be relative to the binary rather than absolute.
"""
rel_tp = os.path.relpath(os.path.join(ROOT, "thirdparty/"),
os.path.dirname(bin_path))
path = re.sub(r".*thirdparty/", "$ORIGIN/"+rel_tp + "/", path)
return path
def fix_rpath(path):
"""
Fix the RPATH/RUNPATH of the binary located at 'path' so that
the thirdparty/ directory is properly found, even though we will
run the binary at a different path than it was originally built.
"""
# Fetch the original rpath.
p = subprocess.Popen(["chrpath", path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
return
rpath = re.search("R(?:UN)?PATH=(.+)", stdout.strip()).group(1)
# Fix it to be relative.
new_path = ":".join(fix_rpath_component(path, c) for c in rpath.split(":"))
# Write the new rpath back into the binary.
subprocess.check_call(["chrpath", "-r", new_path, path])
def fixup_rpaths(root):
"""
Recursively walk the directory tree 'root' and fix the RPATH for any
ELF files (binaries/libraries) that are found.
"""
for dirpath, dirnames, filenames in os.walk(root):
for f in filenames:
p = os.path.join(dirpath, f)
if is_elf_binary(p):
fix_rpath(p)
def find_java():
for x in JAVA_CANDIDATES:
if os.path.exists(x):
logging.info("found JAVA_HOME: ", x)
return os.path.join(x, "bin", "java")
def main():
p = optparse.OptionParser(usage="usage: %prog [options] <test-name>")
p.add_option("-e", "--env", dest="env", type="string", action="append",
help="key=value pairs for environment variables",
default=[])
p.add_option("--collect-tmpdir", dest="collect_tmpdir", action="store_true",
help="whether to collect the test tmpdir as an artifact if the test fails",
default=False)
p.add_option("--test-language", dest="test_language", action="store",
help="java or cpp",
default="cpp")
options, args = p.parse_args()
if len(args) < 1:
p.print_help(sys.stderr)
sys.exit(1)
env = os.environ.copy()
for env_pair in options.env:
(k, v) = env_pair.split("=", 1)
env[k] = v
# Fix the RPATHs of any binaries. During the build, we end up with
# absolute paths from the build machine. This fixes the paths to be
# binary-relative so that we can run it on the new location.
#
# It's important to do this rather than just putting all of the thirdparty
# lib directories into $LD_LIBRARY_PATH below because we need to make sure
# that non-TSAN-instrumented runtime tools (like 'llvm-symbolizer') do _NOT_
# pick up the TSAN-instrumented libraries, whereas TSAN-instrumented test
# binaries (like 'foo_test' or 'kudu-tserver') _DO_ pick them up.
fixup_rpaths(os.path.join(ROOT, "build"))
fixup_rpaths(os.path.join(ROOT, "thirdparty"))
# Override the external_symbolizer_path to use a valid path on the dist-test
# machine. The external_symbolizer_path defined during the build and
# used in sanitizer_options.cc is not valid because it's an absolute path on
# the build machine.
symbolizer_path = os.path.join(ROOT, "thirdparty/installed/uninstrumented/bin/llvm-symbolizer")
for sanitizer in ["ASAN", "LSAN", "MSAN", "TSAN", "UBSAN"]:
var_name = sanitizer + "_OPTIONS"
if "external_symbolizer_path=" not in os.environ.get(var_name, ""):
env[var_name] = os.environ.get(var_name, "") + " external_symbolizer_path=" + symbolizer_path
# Add environment variables for Java dependencies. These environment variables
# are used in mini_hms.cc and mini_ranger.cc.
env['HIVE_HOME'] = glob.glob(os.path.join(ROOT, "thirdparty/src/hive-*"))[0]
env['HADOOP_HOME'] = glob.glob(os.path.join(ROOT, "thirdparty/src/hadoop-*"))[0]
env['RANGER_HOME'] = glob.glob(os.path.join(ROOT, "thirdparty/src/ranger-*"))[0]
env['JAVA_HOME'] = glob.glob("/usr/lib/jvm/java-1.8.0-*")[0]
# Restore the symlinks to the chrony binaries and Postgres and Ranger
# directories; tests expect to find them in same directory as the test
# binaries themselves.
for bin_path in glob.glob(os.path.join(ROOT, "build/*/bin")):
os.symlink(os.path.join(ROOT, "thirdparty/installed/common/bin/chronyc"),
os.path.join(bin_path, "chronyc"))
os.symlink(os.path.join(ROOT, "thirdparty/installed/common/sbin/chronyd"),
os.path.join(bin_path, "chronyd"))
os.symlink(os.path.join(ROOT, "thirdparty/installed/common/bin"),
os.path.join(bin_path, "postgres"))
os.symlink(os.path.join(ROOT, "thirdparty/installed/common/lib"),
os.path.join(bin_path, "postgres-lib"))
os.symlink(os.path.join(ROOT, "thirdparty/installed/common/share/postgresql"),
os.path.join(bin_path, "postgres-share"))
os.symlink(glob.glob(os.path.join(ROOT, "thirdparty/src/postgresql-*/postgresql-*.jar"))[0],
os.path.join(bin_path, "postgresql.jar"))
os.symlink(glob.glob(os.path.join(ROOT, "thirdparty/src/ranger-*"))[0],
os.path.join(bin_path, "ranger-home"))
os.symlink(os.path.join(ROOT, "thirdparty/installed/common/opt/hadoop"),
os.path.join(bin_path, "hadoop-home"))
# When building Ranger, we symlink conf.dist to conf. Overwrite the link we
# copied over with a link that's suitable for the remote machine.
os.unlink(os.path.join(bin_path, "ranger-home/ews/webapp/WEB-INF/classes/conf"))
os.symlink(os.path.join(bin_path, "ranger-home/ews/webapp/WEB-INF/classes/conf.dist"),
os.path.join(bin_path, "ranger-home/ews/webapp/WEB-INF/classes/conf"))
env['LD_LIBRARY_PATH'] = ":".join(
[os.path.join(ROOT, "build/dist-test-system-libs/")] +
glob.glob(os.path.abspath(os.path.join(ROOT, "build/*/lib"))))
# If SASL modules are included in the dist-test-system-libs, set the
# SASL_PATH environment variable to use them instead of the system ones.
sasl_dir = os.path.join(ROOT, "build/dist-test-system-libs/sasl2")
if os.path.exists(sasl_dir):
env['SASL_PATH'] = sasl_dir
# Don't pollute /tmp in dist-test setting. If a test crashes, the dist-test slave
# will clear up our working directory but won't be able to find and clean up things
# left in /tmp.
test_tmpdir = os.path.abspath(os.path.join(ROOT, "test-tmp"))
env['TEST_TMPDIR'] = test_tmpdir
stdout = None
stderr = None
if options.test_language == 'cpp':
cmd = [os.path.join(ROOT, "build-support/run-test.sh")] + args
# Get the grandparent directory of the test executable, which takes the
# form "../release/bin/foo-test", so we can get the build directory.
relative_build_dir = os.path.dirname(os.path.dirname(args[0]))
test_logdir = os.path.abspath(os.path.join(os.getcwd(), relative_build_dir, "test-logs"))
elif options.test_language == 'java':
test_logdir = os.path.abspath(os.path.join(ROOT, "build/java/test-logs"))
if not os.path.exists(test_logdir):
os.makedirs(test_logdir)
if not os.path.exists(test_tmpdir):
os.makedirs(test_tmpdir)
cmd = [find_java()] + args
stdout = stderr = open(os.path.join(test_logdir, "test-output.txt"), "w")
else:
raise ValueError("invalid test language: " + options.test_language)
logging.info("Running command: ", cmd)
logging.info("in dir: ", os.getcwd())
logging.info("Running with env: ", repr(env))
rc = subprocess.call(cmd, env=env, stdout=stdout, stderr=stderr)
if rc != 0 and options.collect_tmpdir:
os.system("tar czf %s %s" % (os.path.join(test_logdir, "test_tmpdir.tgz"), test_tmpdir))
sys.exit(rc)
if __name__ == "__main__":
main()