blob: a55c7236c23afb47707abe5c99fff224b83ab4c3 [file] [log] [blame] [edit]
#!/usr/bin/env python
#
# CTS test for Linaro Android.
#
# Copyright (C) 2010 - 2015, Linaro Limited.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Author: Botao Sun <botao.sun@linaro.org>
# Author: Milosz Wasilewski <milosz.wasilewski@linaro.org>
# Author: Chase Qi <chase.qi@linaro.org>
import datetime
import gzip
import os
import sys
import shlex
import shutil
import subprocess
import threading
import urllib
import xml.etree.ElementTree as ET
import zipfile
import pexpect
import time
CTS_STDOUT = "cts_stdout.txt"
CTS_LOGCAT = "cts_logcat.txt"
class Command(object):
def __init__(self, cmd):
self.cmd = cmd
self.process = None
def run(self, timeout):
def target():
print '%s' % datetime.datetime.now()
self.process = subprocess.Popen(self.cmd, shell=True)
self.process.communicate()
thread = threading.Thread(target=target)
thread.start()
thread.join(timeout)
if thread.is_alive():
print 'Terminating process'
self.process.terminate()
thread.join()
return self.process.returncode
class Heartbeat(threading.Thread):
def __init__(self, serial, process_list):
threading.Thread.__init__(self)
self.serial = serial
self.process_list = process_list
self.adb_ping = Command("adb -s %s shell echo \"OK\"" % serial)
self._finished = threading.Event()
self._interval = 30.0
def setInterval(self, interval):
self._interval = interval
def shutdown(self):
for process in self.process_list:
print "terminating process: %s" % process.pid
if process.poll() is None:
process.kill()
self._finished.set()
def run(self):
while 1:
if self._finished.isSet(): return
return_code = self.adb_ping.run(timeout=10)
if return_code != 0:
# terminate the test as adb connection is lost
print "terminating CTS for %s" % self.serial
for process in self.process_list:
print "terminating process: %s" % process.pid
if process.poll() is None:
process.kill()
self._finished.set()
else:
print "%s is alive" % self.serial
self._finished.wait(self._interval)
# Switch to home path of current user to avoid any permission issue
home_path = os.environ['HOME']
# os.chdir(home_path)
print os.getcwd()
debug_switcher = False
# def collect_result(testcase_id, result):
# if debug_switcher is False:
# subprocess.call(['lava-test-case', testcase_id, '--result', result])
# else:
# print ['lava-test-case', testcase_id, '--result', result]
def result_parser(xml_file):
tree = ET.parse(xml_file)
# dump test result xml to stdout for debug
if debug_switcher is True:
ET.dump(tree)
root = tree.getroot()
print 'There are ' + str(len(root.findall('TestPackage'))) + ' Test Packages in this test result file: ' + xml_file
# testcase_counter = 0
for elem in root.findall('Module'):
# Naming: Package Name + Test Case Name + Test Name
if 'abi' in elem.attrib.keys():
package_name = '.'.join([elem.attrib['abi'], elem.attrib['name']])
else:
package_name = elem.attrib['name']
tests_executed = len(elem.findall('.//Test'))
tests_passed = len(elem.findall('.//Test[@result="pass"]'))
tests_failed = len(elem.findall('.//Test[@result="fail"]'))
subprocess.call(['lava-test-case', package_name + '_executed', '--result', 'pass', '--measurement', str(tests_executed)])
subprocess.call(['lava-test-case', package_name + '_passed', '--result', 'pass', '--measurement', str(tests_passed)])
failed_result = 'pass'
if tests_failed > 0:
failed_result = 'fail'
subprocess.call(['lava-test-case', package_name + '_failed', '--result', failed_result, '--measurement', str(tests_failed)])
# leave the below code for now as commented
# might be used in future (unlikely)
# for testcase in elem.iter('TestCase'):
# testcase_name = testcase.attrib['name']
# for test in testcase.iter('Test'):
# testcase_counter = testcase_counter + 1
# test_name = test.attrib['name']
# testcase_id = '.'.join([package_name, testcase_name, test_name])
# result = test.attrib['result']
# collect_result(testcase_id, result)
# print 'There are ' + str(testcase_counter) + ' test cases in this test result file: ' + xml_file
# download and extract the CTS zip package
ctsurl = sys.argv[1]
# ToDo this might fail and exit ungracefully
ctsfile = urllib.urlretrieve(ctsurl, ctsurl.split('/')[-1])
print "downloaded %s" % sys.argv[1]
print "unzipping %s" % ctsurl.split('/')[-1]
# ToDo this might fail and exit ungracefully
with zipfile.ZipFile(ctsurl.split('/')[-1]) as z:
z.extractall()
z.close()
print "unzipped CTS package"
os.chmod('android-cts/tools/cts-tradefed', 0755)
target_device = sys.argv[2]
# receive user input from JSON file and run
cts_stdout = open(CTS_STDOUT, 'w')
command = 'android-cts/tools/cts-tradefed ' + ' '.join([str(para) for para in sys.argv[3:]])
print command
cts_logcat_out = open(CTS_LOGCAT, 'w')
cts_logcat_command = "adb logcat"
cts_logcat = subprocess.Popen(shlex.split(cts_logcat_command), stdout=cts_logcat_out)
if 'fvp' in open('/tmp/lava_multi_node_cache.txt').read():
# On Fast Models, CTS test will exit abnormally when pipe used(Bug 1904), use
# pexpect here as a work around.
child = pexpect.spawn(command, logfile=cts_stdout)
print 'Starting CTS %s test...' % command.split(' ')[4]
print 'Start time: %s' % datetime.datetime.now()
# Since fvp is slow, give it some time to start the test.
time.sleep(120)
# Send exit command to cts-tf shell, so that TF will exit when remaining
# tests complete.
try:
if not child.expect('cts-tf >'):
child.sendline('exit')
except pexpect.TIMEOUT:
subprocess.call(['lava-test-case', 'CTS-Command-Check', '--result', 'fail'])
print 'Failed to launch CTS shell, exiting...'
sys.exit(1)
while child.isalive():
# When expect([pexpect.EOF]) returns 0, isalive() will be set to Flase.
fvp_adb_check = subprocess.Popen(['adb', '-s', target_device, 'shell', 'echo', 'OK'])
if fvp_adb_check.wait() != 0:
print 'Terminating CTS test as adb connection is lost'
child.terminate(force=True)
subprocess.call(['lava-test-case', 'CTS-Command-Check', '--result', 'fail'])
break
try:
child.expect([pexpect.EOF], timeout=60)
except pexpect.TIMEOUT:
print '%s is running...' % command.split(' ')[4]
print 'End time: %s' % datetime.datetime.now()
cts_logcat.kill()
else:
return_check = subprocess.Popen(shlex.split(command), stdout=cts_stdout)
# start heartbeat process
heartbeat = Heartbeat(target_device, [return_check, cts_logcat])
heartbeat.daemon = True
heartbeat.start()
if return_check.wait() != 0:
# even though the whole command may not run successfully, continue to submit the existing result anyway
# add test case CTS-Command-Check to indicate this incident
print 'CTS command: ' + command + ' run failed!'
# collect_result(testcase_id='CTS-Command-Check', result='fail')
subprocess.call(['lava-test-case', 'CTS-Command-Check', '--result', 'fail'])
heartbeat.shutdown()
cts_logcat_out.close()
cts_stdout.close()
# compress then attach the CTS stdout file to LAVA bundle
with open(CTS_STDOUT, 'rb') as f_in, gzip.open(CTS_STDOUT + '.gz', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
with open(CTS_LOGCAT, 'rb') as f_in, gzip.open(CTS_LOGCAT + '.gz', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
subprocess.call(['lava-test-run-attach', CTS_STDOUT + '.gz'])
subprocess.call(['lava-test-run-attach', CTS_LOGCAT + '.gz'])
# locate and parse the test result
result_dir = 'android-cts/results'
test_result = 'test_result.xml'
if os.path.exists(result_dir) and os.path.isdir(result_dir):
for root, dirs, files in os.walk(result_dir):
for name in files:
if name.endswith(".zip"):
subprocess.call(['lava-test-run-attach', os.path.join(root, name)])
if name == test_result:
result_parser(xml_file=os.path.join(root, name))
# set exit code so LAVA can trust the results
sys.exit(0)