mirror of
https://github.com/apache/impala.git
synced 2025-12-19 18:12:08 -05:00
Python 3 now treats print as a function and requires
the parenthesis in invocation.
print "Hello World!"
is now:
print("Hello World!")
This fixes all locations to use the function
invocation. This is more complicated when the output
is being redirected to a file or when avoiding the
usual newline.
print >> sys.stderr , "Hello World!"
is now:
print("Hello World!", file=sys.stderr)
To support this properly and guarantee equivalent behavior
between python 2 and python 3, all files that use print
now add this import:
from __future__ import print_function
This also fixes random flake8 issues that intersect with
the changes.
Testing:
- check-python-syntax.sh shows no errors related to print
Change-Id: Ib634958369ad777a41e72d80c8053b74384ac351
Reviewed-on: http://gerrit.cloudera.org:8080/19552
Reviewed-by: Joe McDonnell <joemcdonnell@cloudera.com>
Reviewed-by: Michael Smith <michael.smith@cloudera.com>
Tested-by: Michael Smith <michael.smith@cloudera.com>
274 lines
9.8 KiB
Python
Executable File
274 lines
9.8 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# 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.
|
|
|
|
"""
|
|
A script for generating arbitrary junit XML reports while building Impala.
|
|
These files will be consumed by jenkins.impala.io to generate reports for
|
|
easier triaging of build and setup errors.
|
|
"""
|
|
from __future__ import print_function
|
|
import argparse
|
|
import codecs
|
|
import errno
|
|
import os
|
|
import textwrap
|
|
from xml.dom import minidom
|
|
from xml.etree import ElementTree as ET
|
|
|
|
from datetime import datetime as dt
|
|
|
|
IMPALA_HOME = os.getenv('IMPALA_HOME', '.')
|
|
SCRIPT_NAME, _ = os.path.splitext(os.path.basename(__file__))
|
|
JUNITXML_LOGDIR = os.path.join(os.getenv("IMPALA_LOGS_DIR", "."), 'extra_junit_xml_logs')
|
|
|
|
|
|
class JunitReport(object):
|
|
"""A Junit XML style report parseable by Jenkins for reporting build status.
|
|
|
|
Generally, a caller who invokes this script from bash doesn't need to do
|
|
more than supply the necessary command line parameters. The JunitReport
|
|
class is instantiated using those initial inputs, and a timestamped XML
|
|
file is output to the $IMPALA_HOME/logs/extra_junit_xml_logs/.
|
|
|
|
Log files are timestamped, so they will not overwrite previous files containing
|
|
output of the same step.
|
|
|
|
For use from within a python script (must be invoked with impala-python), an
|
|
example might look like:
|
|
|
|
>>> from impala_py_lib.jenkins.generate_junitxml import JunitReport
|
|
>>> report = JunitReport(phase='load_data', step='load_hbase', error_msg='oops')
|
|
>>> report.tofile()
|
|
|
|
For now, the class does not support adding more than one step (analogous to a
|
|
test case) to the same phase (analogous to a test suite). Each report should
|
|
be unique for a given junit XML file. This may be enhanced at some point.
|
|
"""
|
|
|
|
def __init__(self, phase, step, error_msg=None, stdout=None, stderr=None,
|
|
elapsed_time=0):
|
|
|
|
self.root_element = None
|
|
self.testsuite_element = None
|
|
self.testcase_element = None
|
|
|
|
self.phase = phase
|
|
self.step = step
|
|
self.error_msg = error_msg
|
|
self.stdout = stdout
|
|
self.stderr = stderr
|
|
self.elapsed_time = elapsed_time
|
|
self.utc_time = dt.utcnow()
|
|
|
|
self.create_root_element()
|
|
self.add_testsuite_element()
|
|
self.add_testcase_element()
|
|
|
|
if self.error_msg is not None:
|
|
self.set_error()
|
|
|
|
if self.stdout is not None:
|
|
self.add_output('out', self.stdout)
|
|
|
|
if self.stderr is not None:
|
|
self.add_output('err', self.stderr)
|
|
|
|
def create_root_element(self):
|
|
"""Create the testsuites root element."""
|
|
self.root_element = ET.Element("testsuites")
|
|
self.root_element.set("time", "{0:.1f}".format(float(self.elapsed_time)))
|
|
self.root_element.set("tests", "1")
|
|
self.root_element.set("failures", "0")
|
|
self.root_element.set("errors", "0")
|
|
|
|
def add_testsuite_element(self):
|
|
"""Create the testsuite element."""
|
|
self.testsuite_element = ET.SubElement(self.root_element, "testsuite")
|
|
self.testsuite_element.set("name", "{name}.{phase}.{step}".format(
|
|
name=SCRIPT_NAME, phase=self.phase, step=self.step))
|
|
self.testsuite_element.set(
|
|
"timestamp", "{ts}+00:00".format(ts=self.utc_time.strftime('%Y-%m-%d %H:%M:%S')))
|
|
self.testsuite_element.set("disabled", "0")
|
|
self.testsuite_element.set("errors", "0")
|
|
self.testsuite_element.set("failures", "0")
|
|
self.testsuite_element.set("skipped", "0")
|
|
self.testsuite_element.set("tests", "1")
|
|
self.testsuite_element.set("time", "0")
|
|
self.testsuite_element.set("file", "None")
|
|
self.testsuite_element.set("log", "None")
|
|
self.testsuite_element.set("url", "None")
|
|
|
|
def add_testcase_element(self):
|
|
"""Create the testcase element."""
|
|
self.testcase_element = ET.SubElement(self.testsuite_element, "testcase")
|
|
self.testcase_element.set("classname", "{name}.{phase}".format(
|
|
name=SCRIPT_NAME, phase=self.phase))
|
|
self.testcase_element.set("name", self.step)
|
|
|
|
def set_error(self):
|
|
"""Set an error msg if the step failed, and increment necessary error attributes."""
|
|
error = ET.SubElement(self.testcase_element, "error")
|
|
error.set("message", self.error_msg)
|
|
error.set("type", "error")
|
|
self.testsuite_element.set("errors", "1")
|
|
self.root_element.set("errors", "1")
|
|
|
|
def add_output(self, output_type, file_or_string):
|
|
"""
|
|
Add stdout or stderr content to testcase element.
|
|
|
|
Args:
|
|
output_type: [string] either out or err
|
|
file_or_string: a path to a file containing the content, or a plain string
|
|
"""
|
|
output = ET.SubElement(self.testcase_element,
|
|
"system-{output_type}".format(output_type=output_type))
|
|
output.text = JunitReport.get_xml_content(file_or_string)
|
|
|
|
def to_file(self, junitxml_logdir=JUNITXML_LOGDIR):
|
|
"""
|
|
Create a timestamped XML report file.
|
|
|
|
Args:
|
|
junitxml_logdir: path to directory where the file will be created
|
|
|
|
Return:
|
|
junit_log_file: path to the generated file
|
|
"""
|
|
# The equivalent of mkdir -p
|
|
try:
|
|
os.makedirs(junitxml_logdir)
|
|
except OSError as e:
|
|
if e.errno == errno.EEXIST and os.path.isdir(junitxml_logdir):
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
filename = '{name}.{ts}.xml'.format(
|
|
name=self.testsuite_element.attrib['name'],
|
|
ts=self.utc_time.strftime('%Y%m%d_%H_%M_%S')
|
|
)
|
|
junit_log_file = os.path.join(junitxml_logdir, filename)
|
|
|
|
with codecs.open(junit_log_file, encoding="UTF-8", mode='w') as f:
|
|
f.write(unicode(self))
|
|
|
|
return junit_log_file
|
|
|
|
@staticmethod
|
|
def get_xml_content(file_or_string=None):
|
|
"""
|
|
Derive additional content for the XML report.
|
|
|
|
If the supplied parameter is the path to a file, the contents will be inserted
|
|
into the XML report. If the parameter is just plain string, use that as the
|
|
content for the report. For a file or a string passed in on the commandline,
|
|
this assumes it could contain Unicode content and converts it to a Unicode
|
|
object.
|
|
|
|
Args:
|
|
file_or_string: a path to a file, or a plain string
|
|
|
|
Returns:
|
|
content as a unicode object
|
|
"""
|
|
if file_or_string is None:
|
|
content = u''
|
|
elif os.path.exists(file_or_string):
|
|
with codecs.open(file_or_string, encoding="UTF-8", mode='r') as f:
|
|
content = f.read()
|
|
else:
|
|
# This is a string passed in on the command line. Make sure to return it as
|
|
# a unicode string.
|
|
content = unicode(file_or_string, encoding="UTF-8")
|
|
return content
|
|
|
|
def __unicode__(self):
|
|
"""
|
|
Generate and return a pretty-printable XML unicode string
|
|
"""
|
|
root_node_unicode = ET.tostring(self.root_element)
|
|
root_node_dom = minidom.parseString(root_node_unicode)
|
|
return root_node_dom.toprettyxml(indent=' ' * 4)
|
|
|
|
|
|
def get_options():
|
|
"""Parse and return command line options."""
|
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
|
|
# Required options
|
|
parser.add_argument("--phase",
|
|
default="buildall",
|
|
help="General build phase or script.")
|
|
parser.add_argument("--step",
|
|
required=True,
|
|
help=textwrap.dedent(
|
|
"""Specific build step or child script being run.
|
|
Each step must be unique for the given build phase.""")
|
|
)
|
|
parser.add_argument("-t", "--time",
|
|
type=float,
|
|
default=0,
|
|
help="If known, the elapsed time in seconds for this step.")
|
|
parser.add_argument("--stdout",
|
|
help=textwrap.dedent(
|
|
"""Standard output to include in the XML report. Can be
|
|
either a string or the path to a file.""")
|
|
)
|
|
parser.add_argument("--stderr",
|
|
help=textwrap.dedent(
|
|
"""Standard error to include in the XML report. Can be
|
|
either a string or the path to a file.""")
|
|
)
|
|
parser.add_argument("--error",
|
|
help=textwrap.dedent(
|
|
"""If specified, the XML report will mark this as an error.
|
|
This should be a brief explanation for the error.""")
|
|
)
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
"""
|
|
Create a report for each invocation of the script, and output the results
|
|
of the test case to an XML file within $IMPALA_HOME/logs/extra_junit_xml_logs.
|
|
The log file name will use "phase" and "step" values provided on the command
|
|
line to structure the report. The XML report filename will follow the form:
|
|
|
|
junitxml_logger.<phase>.<step>.<time_stamp>.xml
|
|
|
|
Phase can be repeated in a given test run, but the step leaf node, which is
|
|
equivalent to a "test case", must be unique within each phase.
|
|
"""
|
|
options = get_options()
|
|
|
|
junit_report = JunitReport(phase=options.phase,
|
|
step=options.step,
|
|
error_msg=options.error,
|
|
stdout=options.stdout,
|
|
stderr=options.stderr,
|
|
elapsed_time=options.time)
|
|
|
|
junit_log_file = junit_report.to_file()
|
|
print("Generated: {0}".format(junit_log_file))
|
|
|
|
|
|
if "__main__" == __name__:
|
|
main()
|