Add test_skia.py, isolates for test_skia, images, skps

This enables running DM through Swarming.

NOTRY=true
BUG=skia:4763
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1743113003

Review URL: https://codereview.chromium.org/1743113003
This commit is contained in:
borenet 2016-02-29 05:57:31 -08:00 committed by Commit bot
parent 6bc967984a
commit a7a6f2e01d
10 changed files with 349 additions and 16 deletions

View File

@ -6,9 +6,15 @@
# found in the LICENSE file. # found in the LICENSE file.
import contextlib
import math
import os import os
import shutil
import socket
import subprocess import subprocess
import sys import sys
import time
import urllib2
from flavor import android_flavor from flavor import android_flavor
from flavor import chromeos_flavor from flavor import chromeos_flavor
@ -29,15 +35,23 @@ GM_ACTUAL_FILENAME = 'actual-results.json'
GM_EXPECTATIONS_FILENAME = 'expected-results.json' GM_EXPECTATIONS_FILENAME = 'expected-results.json'
GM_IGNORE_TESTS_FILENAME = 'ignored-tests.txt' GM_IGNORE_TESTS_FILENAME = 'ignored-tests.txt'
GOLD_UNINTERESTING_HASHES_URL = 'https://gold.skia.org/_/hashes'
GS_GM_BUCKET = 'chromium-skia-gm' GS_GM_BUCKET = 'chromium-skia-gm'
GS_SUMMARIES_BUCKET = 'chromium-skia-gm-summaries' GS_SUMMARIES_BUCKET = 'chromium-skia-gm-summaries'
GS_SUBDIR_TMPL_SK_IMAGE = 'skimage/v%s'
GS_SUBDIR_TMPL_SKP = 'playback_%s/skps'
SKIA_REPO = 'https://skia.googlesource.com/skia.git' SKIA_REPO = 'https://skia.googlesource.com/skia.git'
INFRA_REPO = 'https://skia.googlesource.com/buildbot.git' INFRA_REPO = 'https://skia.googlesource.com/buildbot.git'
SERVICE_ACCOUNT_FILE = 'service-account-skia.json' SERVICE_ACCOUNT_FILE = 'service-account-skia.json'
SERVICE_ACCOUNT_INTERNAL_FILE = 'service-account-skia-internal.json' SERVICE_ACCOUNT_INTERNAL_FILE = 'service-account-skia-internal.json'
VERSION_FILE_SK_IMAGE = 'SK_IMAGE_VERSION'
VERSION_FILE_SKP = 'SKP_VERSION'
def is_android(bot_cfg): def is_android(bot_cfg):
"""Determine whether the given bot is an Android bot.""" """Determine whether the given bot is an Android bot."""
@ -66,21 +80,84 @@ def is_xsan(bot_cfg):
bot_cfg.get('extra_config') == 'TSAN') bot_cfg.get('extra_config') == 'TSAN')
def download_dir(skia_dir, tmp_dir, version_file, gs_path_tmpl, dst_dir):
# Ensure that the tmp_dir exists.
if not os.path.isdir(tmp_dir):
os.makedirs(tmp_dir)
# Get the expected version.
with open(os.path.join(skia_dir, version_file)) as f:
expected_version = f.read().rstrip()
print 'Expected %s = %s' % (version_file, expected_version)
# Get the actually-downloaded version, if we have one.
actual_version_file = os.path.join(tmp_dir, version_file)
try:
with open(actual_version_file) as f:
actual_version = f.read().rstrip()
except IOError:
actual_version = -1
print 'Actual %s = %s' % (version_file, actual_version)
# If we don't have the desired version, download it.
if actual_version != expected_version:
if actual_version != -1:
os.remove(actual_version_file)
if os.path.isdir(dst_dir):
shutil.rmtree(dst_dir)
os.makedirs(dst_dir)
gs_path = 'gs://%s/%s/*' % (GS_GM_BUCKET, gs_path_tmpl % expected_version)
print 'Downloading from %s' % gs_path
subprocess.check_call(['gsutil', 'cp', '-R', gs_path, dst_dir])
with open(actual_version_file, 'w') as f:
f.write(expected_version)
def get_uninteresting_hashes(hashes_file):
retries = 5
timeout = 60
wait_base = 15
socket.setdefaulttimeout(timeout)
for retry in range(retries):
try:
with contextlib.closing(
urllib2.urlopen(GOLD_UNINTERESTING_HASHES_URL, timeout=timeout)) as w:
hashes = w.read()
with open(hashes_file, 'w') as f:
f.write(hashes)
break
except Exception as e:
print >> sys.stderr, 'Failed to get uninteresting hashes from %s:\n%s' % (
GOLD_UNINTERESTING_HASHES_URL, e)
if retry == retries:
raise
waittime = wait_base * math.pow(2, retry)
print 'Retry in %d seconds.' % waittime
time.sleep(waittime)
class BotInfo(object): class BotInfo(object):
def __init__(self, bot_name, slave_name, out_dir): def __init__(self, bot_name, swarm_out_dir):
"""Initialize the bot, given its name. """Initialize the bot, given its name.
Assumes that CWD is the directory containing this file. Assumes that CWD is the directory containing this file.
""" """
self.name = bot_name self.name = bot_name
self.slave_name = slave_name
self.skia_dir = os.path.abspath(os.path.join( self.skia_dir = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)), os.path.dirname(os.path.realpath(__file__)),
os.pardir, os.pardir)) os.pardir, os.pardir))
self.swarm_out_dir = swarm_out_dir
os.chdir(self.skia_dir) os.chdir(self.skia_dir)
self.build_dir = os.path.abspath(os.path.join(self.skia_dir, os.pardir)) self.build_dir = os.path.abspath(os.path.join(self.skia_dir, os.pardir))
self.out_dir = out_dir
self.spec = self.get_bot_spec(bot_name) self.spec = self.get_bot_spec(bot_name)
self.bot_cfg = self.spec['builder_cfg']
if self.bot_cfg['role'] == 'Build':
self.out_dir = os.path.join(swarm_out_dir, 'out')
else:
self.out_dir = 'out'
self.configuration = self.spec['configuration'] self.configuration = self.spec['configuration']
self.default_env = { self.default_env = {
'SKIA_OUT': self.out_dir, 'SKIA_OUT': self.out_dir,
@ -89,16 +166,29 @@ class BotInfo(object):
} }
self.default_env.update(self.spec['env']) self.default_env.update(self.spec['env'])
self.build_targets = [str(t) for t in self.spec['build_targets']] self.build_targets = [str(t) for t in self.spec['build_targets']]
self.bot_cfg = self.spec['builder_cfg']
self.is_trybot = self.bot_cfg['is_trybot'] self.is_trybot = self.bot_cfg['is_trybot']
self.upload_dm_results = self.spec['upload_dm_results'] self.upload_dm_results = self.spec['upload_dm_results']
self.upload_perf_results = self.spec['upload_perf_results'] self.upload_perf_results = self.spec['upload_perf_results']
self.perf_data_dir = os.path.join(self.swarm_out_dir, 'perfdata',
self.name, 'data')
self.resource_dir = os.path.join(self.build_dir, 'resources')
self.images_dir = os.path.join(self.build_dir, 'images')
self.local_skp_dir = os.path.join(self.build_dir, 'playback', 'skps')
self.dm_flags = self.spec['dm_flags'] self.dm_flags = self.spec['dm_flags']
self.nanobench_flags = self.spec['nanobench_flags'] self.nanobench_flags = self.spec['nanobench_flags']
self._ccache = None self._ccache = None
self._checked_for_ccache = False self._checked_for_ccache = False
self._already_ran = {}
self.tmp_dir = os.path.join(self.build_dir, 'tmp')
self.flavor = self.get_flavor(self.bot_cfg) self.flavor = self.get_flavor(self.bot_cfg)
# These get filled in during subsequent steps.
self.device_dirs = None
self.build_number = None
self.got_revision = None
self.master_name = None
self.slave_name = None
@property @property
def ccache(self): def ccache(self):
if not self._checked_for_ccache: if not self._checked_for_ccache:
@ -148,3 +238,131 @@ class BotInfo(object):
print 'ENV: %s' % _env print 'ENV: %s' % _env
print '============' print '============'
subprocess.check_call(cmd, env=_env, cwd=cwd) subprocess.check_call(cmd, env=_env, cwd=cwd)
def compile_steps(self):
for t in self.build_targets:
self.flavor.compile(t)
def _run_once(self, fn, *args, **kwargs):
if not fn.__name__ in self._already_ran:
self._already_ran[fn.__name__] = True
fn(*args, **kwargs)
def install(self):
"""Copy the required executables and files to the device."""
self.device_dirs = self.flavor.get_device_dirs()
# Run any device-specific installation.
self.flavor.install()
# TODO(borenet): Only copy files which have changed.
# Resources
self.flavor.copy_directory_contents_to_device(self.resource_dir,
self.device_dirs.resource_dir)
def _key_params(self):
"""Build a unique key from the builder name (as a list).
E.g. arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6
"""
# Don't bother to include role, which is always Test.
# TryBots are uploaded elsewhere so they can use the same key.
blacklist = ['role', 'is_trybot']
flat = []
for k in sorted(self.bot_cfg.keys()):
if k not in blacklist:
flat.append(k)
flat.append(self.bot_cfg[k])
return flat
def test_steps(self, got_revision, master_name, slave_name, build_number):
"""Run the DM test."""
self.build_number = build_number
self.got_revision = got_revision
self.master_name = master_name
self.slave_name = slave_name
self._run_once(self.install)
use_hash_file = False
if self.upload_dm_results:
# This must run before we write anything into self.device_dirs.dm_dir
# or we may end up deleting our output on machines where they're the same.
host_dm_dir = os.path.join(self.swarm_out_dir, 'dm')
print 'host dm dir: %s' % host_dm_dir
self.flavor.create_clean_host_dir(host_dm_dir)
if str(host_dm_dir) != str(self.device_dirs.dm_dir):
self.flavor.create_clean_device_dir(self.device_dirs.dm_dir)
# Obtain the list of already-generated hashes.
hash_filename = 'uninteresting_hashes.txt'
host_hashes_file = self.tmp_dir.join(hash_filename)
hashes_file = self.flavor.device_path_join(
self.device_dirs.tmp_dir, hash_filename)
try:
get_uninteresting_hashes(host_hashes_file)
except Exception:
pass
if os.path.exists(host_hashes_file):
self.flavor.copy_file_to_device(host_hashes_file, hashes_file)
use_hash_file = True
# Run DM.
properties = [
'gitHash', self.got_revision,
'master', self.master_name,
'builder', self.name,
'build_number', self.build_number,
]
if self.is_trybot:
properties.extend([
'issue', self.m.properties['issue'],
'patchset', self.m.properties['patchset'],
])
args = [
'dm',
'--undefok', # This helps branches that may not know new flags.
'--verbose',
'--resourcePath', self.device_dirs.resource_dir,
'--skps', self.device_dirs.skp_dir,
'--images', self.flavor.device_path_join(
self.device_dirs.images_dir, 'dm'),
'--nameByHash',
'--properties'
] + properties
args.append('--key')
args.extend(self._key_params())
if use_hash_file:
args.extend(['--uninterestingHashesFile', hashes_file])
if self.upload_dm_results:
args.extend(['--writePath', self.device_dirs.dm_dir])
skip_flag = None
if self.bot_cfg.get('cpu_or_gpu') == 'CPU':
skip_flag = '--nogpu'
elif self.bot_cfg.get('cpu_or_gpu') == 'GPU':
skip_flag = '--nocpu'
if skip_flag:
args.append(skip_flag)
args.extend(self.dm_flags)
self.flavor.run(args, env=self.default_env)
if self.upload_dm_results:
# Copy images and JSON to host machine if needed.
self.flavor.copy_directory_contents_to_host(self.device_dirs.dm_dir,
host_dm_dir)
# See skia:2789.
if ('Valgrind' in self.name and
self.builder_cfg.get('cpu_or_gpu') == 'GPU'):
abandonGpuContext = list(args)
abandonGpuContext.append('--abandonGpuContext')
self.flavor.run(abandonGpuContext)
preAbandonGpuContext = list(args)
preAbandonGpuContext.append('--preAbandonGpuContext')
self.flavor.run(preAbandonGpuContext)

View File

@ -4,7 +4,7 @@
], ],
'variables': { 'variables': {
'command': [ 'command': [
'python', 'compile_skia.py', '<(BUILDER_NAME)', '${ISOLATED_OUTDIR}/out', 'python', 'compile_skia.py', '--builder_name', '<(BUILDER_NAME)', '--swarm_out_dir', '${ISOLATED_OUTDIR}/out',
], ],
}, },
} }

View File

@ -6,17 +6,19 @@
# found in the LICENSE file. # found in the LICENSE file.
import argparse
import common import common
import os
import sys import sys
def main(): def main():
if len(sys.argv) != 3: parser = argparse.ArgumentParser()
print >> sys.stderr, 'Usage: compile_skia.py <builder name> <out-dir>' parser.add_argument('--builder_name', required=True)
sys.exit(1) parser.add_argument('--swarm_out_dir', required=True)
bot = common.BotInfo(sys.argv[1], 'fake-slave', sys.argv[2]) args = parser.parse_args()
for t in bot.build_targets: bot = common.BotInfo(args.builder_name, os.path.abspath(args.swarm_out_dir))
bot.flavor.compile(t) bot.compile_steps()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
#
# Copyright 2016 Google Inc.
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import common
import os
import sys
def main():
if len(sys.argv) != 1:
print >> sys.stderr, 'Usage: download_images.py'
sys.exit(1)
skia_dir = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)),
os.pardir, os.pardir))
dst_dir = os.path.join(skia_dir, os.pardir, 'images')
tmp_dir = os.path.join(skia_dir, os.pardir, 'tmp')
common.download_dir(skia_dir, tmp_dir, common.VERSION_FILE_SK_IMAGE,
common.GS_SUBDIR_TMPL_SK_IMAGE, dst_dir)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
#
# Copyright 2016 Google Inc.
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import common
import os
import sys
def main():
if len(sys.argv) != 1:
print >> sys.stderr, 'Usage: download_skps.py'
sys.exit(1)
skia_dir = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)),
os.pardir, os.pardir))
dst_dir = os.path.join(skia_dir, os.pardir, 'skps')
tmp_dir = os.path.join(skia_dir, os.pardir, 'tmp')
common.download_dir(skia_dir, tmp_dir, common.VERSION_FILE_SKP,
common.GS_SUBDIR_TMPL_SKP, dst_dir)
if __name__ == '__main__':
main()

View File

@ -71,10 +71,10 @@ class DefaultFlavorUtils(object):
self._bot_info = bot_info self._bot_info = bot_info
self.chrome_path = os.path.join(os.path.expanduser('~'), 'src') self.chrome_path = os.path.join(os.path.expanduser('~'), 'src')
def step(self, cmd, **kwargs): def run(self, cmd, **kwargs):
"""Runs a step as appropriate for this flavor.""" """Runs a step as appropriate for this flavor."""
path_to_app = self._bot_info.out_dir.join( path_to_app = os.path.join(self._bot_info.out_dir,
self._bot_info.configuration, cmd[0]) self._bot_info.configuration, cmd[0])
if (sys.platform == 'linux' and if (sys.platform == 'linux' and
'x86_64' in self._bot_info.bot_name and 'x86_64' in self._bot_info.bot_name and
not 'TSAN' in self._bot_info.bot_name): not 'TSAN' in self._bot_info.bot_name):
@ -141,7 +141,8 @@ class DefaultFlavorUtils(object):
def create_clean_host_dir(self, path): def create_clean_host_dir(self, path):
"""Convenience function for creating a clean directory.""" """Convenience function for creating a clean directory."""
shutil.rmtree(path) if os.path.exists(path):
shutil.rmtree(path)
os.makedirs(path) os.makedirs(path)
def install(self): def install(self):
@ -161,7 +162,7 @@ class DefaultFlavorUtils(object):
""" """
join = lambda p: os.path.join(self._bot_info.build_dir, p) join = lambda p: os.path.join(self._bot_info.build_dir, p)
return DeviceDirs( return DeviceDirs(
dm_dir=join('dm'), dm_dir=os.path.join(self._bot_info.swarm_out_dir, 'dm'),
perf_data_dir=self._bot_info.perf_data_dir, perf_data_dir=self._bot_info.perf_data_dir,
resource_dir=self._bot_info.resource_dir, resource_dir=self._bot_info.resource_dir,
images_dir=join('images'), images_dir=join('images'),

View File

@ -0,0 +1,7 @@
{
'variables': {
'files': [
'../../../images/',
],
},
}

7
infra/bots/skps.isolate Normal file
View File

@ -0,0 +1,7 @@
{
'variables': {
'files': [
'../../../skps/',
],
},
}

View File

@ -0,0 +1,12 @@
{
'includes': [
'images.isolate',
'skia_repo.isolate',
'skps.isolate',
],
'variables': {
'command': [
'python', 'test_skia.py', '--master_name', '<(MASTER_NAME)', '--builder_name', '<(BUILDER_NAME)', '--build_number', '<(BUILD_NUMBER)', '--slave_name', '<(SLAVE_NAME)', '--revision', '<(REVISION)', '--swarm_out_dir', '${ISOLATED_OUTDIR}',
],
},
}

30
infra/bots/test_skia.py Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env python
#
# Copyright 2016 Google Inc.
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import common
import os
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--master_name', required=True)
parser.add_argument('--builder_name', required=True)
parser.add_argument('--build_number', required=True)
parser.add_argument('--slave_name', required=True)
parser.add_argument('--revision', required=True)
parser.add_argument('--swarm_out_dir', required=True)
args = parser.parse_args()
bot = common.BotInfo(args.builder_name, os.path.abspath(args.swarm_out_dir))
bot.test_steps(args.revision, args.master_name, args.slave_name,
args.build_number)
if __name__ == '__main__':
main()