Bug 1382775 - Move autotry logic from |mach try| into autotry proper, r=chmanchester

This is a straightforward copy of code from the mach_commands.py to autotry.py

MozReview-Commit-ID: 7TkbTff0Tv8

--HG--
extra : rebase_source : 7996131427217d9f0213af920d0d4ef0d2e7d0ac
extra : source : f0693a73539265f74f79db9d1e136e7f1c16a1f0
This commit is contained in:
Andrew Halberstadt 2017-07-18 08:52:42 -04:00
Родитель 7b130edcb5
Коммит 2a6c9e6348
2 изменённых файлов: 218 добавлений и 198 удалений

Просмотреть файл

@ -4,27 +4,17 @@
from __future__ import absolute_import, print_function, unicode_literals from __future__ import absolute_import, print_function, unicode_literals
import os import argparse
import sys
from collections import defaultdict
from mach.decorators import ( from mach.decorators import (
CommandArgument,
CommandProvider, CommandProvider,
Command, Command,
SubCommand,
) )
import mozpack.path as mozpath
from mozbuild.base import BuildEnvironmentNotFoundException, MachCommandBase from mozbuild.base import BuildEnvironmentNotFoundException, MachCommandBase
CONFIG_ENVIRONMENT_NOT_FOUND = '''
No config environment detected. This means we are unable to properly
detect test files in the specified paths or tags. Please run:
$ mach configure
and try again.
'''.lstrip()
def syntax_parser(): def syntax_parser():
from tryselect.selectors.syntax import arg_parser from tryselect.selectors.syntax import arg_parser
@ -43,95 +33,35 @@ def syntax_parser():
@CommandProvider @CommandProvider
class PushToTry(MachCommandBase): class TrySelect(MachCommandBase):
def normalise_list(self, items, allow_subitems=False):
from tryselect.selectors.syntax import parse_arg
rv = defaultdict(list)
for item in items:
parsed = parse_arg(item)
for key, values in parsed.iteritems():
rv[key].extend(values)
if not allow_subitems:
if not all(item == [] for item in rv.itervalues()):
raise ValueError("Unexpected subitems in argument")
return rv.keys()
else:
return rv
def validate_args(self, **kwargs):
from tryselect.selectors.syntax import AutoTry
tests_selected = kwargs["tests"] or kwargs["paths"] or kwargs["tags"]
if kwargs["platforms"] is None and (kwargs["jobs"] is None or tests_selected):
if 'AUTOTRY_PLATFORM_HINT' in os.environ:
kwargs["platforms"] = [os.environ['AUTOTRY_PLATFORM_HINT']]
elif tests_selected:
print("Must specify platform when selecting tests.")
sys.exit(1)
else:
print("Either platforms or jobs must be specified as an argument to "
"|mach try syntax|.")
sys.exit(1)
try:
platforms = (self.normalise_list(kwargs["platforms"])
if kwargs["platforms"] else {})
except ValueError as e:
print("Error parsing -p argument:\n%s" % e.message)
sys.exit(1)
try:
tests = (self.normalise_list(kwargs["tests"], allow_subitems=True)
if kwargs["tests"] else {})
except ValueError as e:
print("Error parsing -u argument (%s):\n%s" % (kwargs["tests"], e.message))
sys.exit(1)
try:
talos = (self.normalise_list(kwargs["talos"], allow_subitems=True)
if kwargs["talos"] else [])
except ValueError as e:
print("Error parsing -t argument:\n%s" % e.message)
sys.exit(1)
try:
jobs = (self.normalise_list(kwargs["jobs"]) if kwargs["jobs"] else {})
except ValueError as e:
print("Error parsing -j argument:\n%s" % e.message)
sys.exit(1)
paths = []
for p in kwargs["paths"]:
p = mozpath.normpath(os.path.abspath(p))
if not (os.path.isdir(p) and p.startswith(self.topsrcdir)):
print('Specified path "%s" is not a directory under the srcdir,'
' unable to specify tests outside of the srcdir' % p)
sys.exit(1)
if len(p) <= len(self.topsrcdir):
print('Specified path "%s" is at the top of the srcdir and would'
' select all tests.' % p)
sys.exit(1)
paths.append(os.path.relpath(p, self.topsrcdir))
try:
tags = self.normalise_list(kwargs["tags"]) if kwargs["tags"] else []
except ValueError as e:
print("Error parsing --tags argument:\n%s" % e.message)
sys.exit(1)
extra_values = {k['dest'] for k in AutoTry.pass_through_arguments.values()}
extra_args = {k: v for k, v in kwargs.items()
if k in extra_values and v}
return kwargs["builds"], platforms, tests, talos, jobs, paths, tags, extra_args
@Command('try', @Command('try',
category='testing', category='ci',
description='Push selected tests to the try server', description='Push selected tasks to the try server')
@CommandArgument('args', nargs=argparse.REMAINDER)
def try_default(self, args):
"""Push selected tests to the try server.
The |mach try| command is a frontend for scheduling tasks to
run on try server using selectors. A selector is a subcommand
that provides its own set of command line arguments and are
listed below. Currently there is only single selector called
`syntax`, but more selectors will be added in the future.
If no subcommand is specified, the `syntax` selector is run by
default. Run |mach try syntax --help| for more information on
scheduling with the `syntax` selector.
"""
parser = syntax_parser()
kwargs = vars(parser.parse_args(args))
return self._mach_context.commands.dispatch(
'try', subcommand='syntax', context=self._mach_context, **kwargs)
@SubCommand('try',
'syntax',
description='Push selected tasks using try syntax',
parser=syntax_parser) parser=syntax_parser)
def syntax(self, **kwargs): def try_syntax(self, **kwargs):
"""Push the current tree to try, with the specified syntax. """Push the current tree to try, with the specified syntax.
Build options, platforms and regression tests may be selected Build options, platforms and regression tests may be selected
@ -169,107 +99,20 @@ class PushToTry(MachCommandBase):
(available at https://github.com/glandium/git-cinnabar). (available at https://github.com/glandium/git-cinnabar).
""" """
from mozbuild.testing import TestResolver from mozbuild.testing import TestResolver
from tryselect.selectors.syntax import AutoTry from tryselect.selectors.syntax import AutoTry
print("mach try is under development, please file bugs blocking 1149670.")
def resolver_func():
return self._spawn(TestResolver)
at = AutoTry(self.topsrcdir, resolver_func, self._mach_context)
if kwargs["list"]:
at.list_presets()
sys.exit()
if kwargs["load"] is not None:
defaults = at.load_config(kwargs["load"])
if defaults is None:
print("No saved configuration called %s found in autotry.ini" % kwargs["load"],
file=sys.stderr)
for key, value in kwargs.iteritems():
if value in (None, []) and key in defaults:
kwargs[key] = defaults[key]
if kwargs["push"] and at.find_uncommited_changes():
print('ERROR please commit changes before continuing')
sys.exit(1)
if not any(kwargs[item] for item in ("paths", "tests", "tags")):
kwargs["paths"], kwargs["tags"] = at.find_paths_and_tags(kwargs["verbose"])
builds, platforms, tests, talos, jobs, paths, tags, extra = self.validate_args(**kwargs)
if paths or tags:
if not os.path.exists(os.path.join(self.topobjdir, 'config.status')):
print(CONFIG_ENVIRONMENT_NOT_FOUND)
sys.exit(1)
paths = [os.path.relpath(os.path.normpath(os.path.abspath(item)), self.topsrcdir)
for item in paths]
paths_by_flavor = at.paths_by_flavor(paths=paths, tags=tags)
if not paths_by_flavor and not tests:
print("No tests were found when attempting to resolve paths:\n\n\t%s" %
paths)
sys.exit(1)
if not kwargs["intersection"]:
paths_by_flavor = at.remove_duplicates(paths_by_flavor, tests)
else:
paths_by_flavor = {}
# No point in dealing with artifacts if we aren't running any builds
local_artifact_build = False
if platforms:
try: try:
if self.substs.get("MOZ_ARTIFACT_BUILDS"): if self.substs.get("MOZ_ARTIFACT_BUILDS"):
local_artifact_build = True kwargs['local_artifact_build'] = True
except BuildEnvironmentNotFoundException: except BuildEnvironmentNotFoundException:
# If we don't have a build locally, we can't tell whether # If we don't have a build locally, we can't tell whether
# an artifact build is desired, but we still want the # an artifact build is desired, but we still want the
# command to succeed, if possible. # command to succeed, if possible.
pass pass
# Add --artifact if --enable-artifact-builds is set ... def resolver_func():
if local_artifact_build: return self._spawn(TestResolver)
extra["artifact"] = True
# ... unless --no-artifact is explicitly given.
if kwargs["no_artifact"]:
if "artifact" in extra:
del extra["artifact"]
try: at = AutoTry(self.topsrcdir, resolver_func, self._mach_context)
msg = at.calc_try_syntax(platforms, tests, talos, jobs, builds, paths_by_flavor, tags, return at.run(**kwargs)
extra, kwargs["intersection"])
except ValueError as e:
print(e.message)
sys.exit(1)
if local_artifact_build:
if kwargs["no_artifact"]:
print('mozconfig has --enable-artifact-builds but '
'--no-artifact specified, not including --artifact '
'flag in try syntax')
else:
print('mozconfig has --enable-artifact-builds; including '
'--artifact flag in try syntax (use --no-artifact '
'to override)')
if kwargs["verbose"] and paths_by_flavor:
print('The following tests will be selected: ')
for flavor, paths in paths_by_flavor.iteritems():
print("%s: %s" % (flavor, ",".join(paths)))
if kwargs["verbose"] or not kwargs["push"]:
print('The following try syntax was calculated:\n%s' % msg)
if kwargs["push"]:
at.push_to_try(msg, kwargs["verbose"])
if kwargs["save"] is not None:
at.save_config(kwargs["save"], msg)

Просмотреть файл

@ -2,16 +2,27 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
import ConfigParser
import argparse import argparse
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import which import which
from collections import defaultdict from collections import defaultdict
import ConfigParser import mozpack.path as mozpath
CONFIG_ENVIRONMENT_NOT_FOUND = '''
No config environment detected. This means we are unable to properly
detect test files in the specified paths or tags. Please run:
$ mach configure
and try again.
'''.lstrip()
def arg_parser(): def arg_parser():
@ -613,3 +624,169 @@ class AutoTry(object):
print("Pushing tests based on the following tags:\n\t%s" % print("Pushing tests based on the following tags:\n\t%s" %
"\n\t".join(tags)) "\n\t".join(tags))
return paths, tags return paths, tags
def normalise_list(self, items, allow_subitems=False):
rv = defaultdict(list)
for item in items:
parsed = parse_arg(item)
for key, values in parsed.iteritems():
rv[key].extend(values)
if not allow_subitems:
if not all(item == [] for item in rv.itervalues()):
raise ValueError("Unexpected subitems in argument")
return rv.keys()
else:
return rv
def validate_args(self, **kwargs):
tests_selected = kwargs["tests"] or kwargs["paths"] or kwargs["tags"]
if kwargs["platforms"] is None and (kwargs["jobs"] is None or tests_selected):
if 'AUTOTRY_PLATFORM_HINT' in os.environ:
kwargs["platforms"] = [os.environ['AUTOTRY_PLATFORM_HINT']]
elif tests_selected:
print("Must specify platform when selecting tests.")
sys.exit(1)
else:
print("Either platforms or jobs must be specified as an argument to autotry.")
sys.exit(1)
try:
platforms = (self.normalise_list(kwargs["platforms"])
if kwargs["platforms"] else {})
except ValueError as e:
print("Error parsing -p argument:\n%s" % e.message)
sys.exit(1)
try:
tests = (self.normalise_list(kwargs["tests"], allow_subitems=True)
if kwargs["tests"] else {})
except ValueError as e:
print("Error parsing -u argument (%s):\n%s" % (kwargs["tests"], e.message))
sys.exit(1)
try:
talos = (self.normalise_list(kwargs["talos"], allow_subitems=True)
if kwargs["talos"] else [])
except ValueError as e:
print("Error parsing -t argument:\n%s" % e.message)
sys.exit(1)
try:
jobs = (self.normalise_list(kwargs["jobs"]) if kwargs["jobs"] else {})
except ValueError as e:
print("Error parsing -j argument:\n%s" % e.message)
sys.exit(1)
paths = []
for p in kwargs["paths"]:
p = mozpath.normpath(os.path.abspath(p))
if not (os.path.isdir(p) and p.startswith(self.topsrcdir)):
print('Specified path "%s" is not a directory under the srcdir,'
' unable to specify tests outside of the srcdir' % p)
sys.exit(1)
if len(p) <= len(self.topsrcdir):
print('Specified path "%s" is at the top of the srcdir and would'
' select all tests.' % p)
sys.exit(1)
paths.append(os.path.relpath(p, self.topsrcdir))
try:
tags = self.normalise_list(kwargs["tags"]) if kwargs["tags"] else []
except ValueError as e:
print("Error parsing --tags argument:\n%s" % e.message)
sys.exit(1)
extra_values = {k['dest'] for k in AutoTry.pass_through_arguments.values()}
extra_args = {k: v for k, v in kwargs.items()
if k in extra_values and v}
return kwargs["builds"], platforms, tests, talos, jobs, paths, tags, extra_args
def run(self, **kwargs):
if kwargs["list"]:
self.list_presets()
sys.exit()
if kwargs["load"] is not None:
defaults = self.load_config(kwargs["load"])
if defaults is None:
print("No saved configuration called %s found in autotry.ini" % kwargs["load"],
file=sys.stderr)
for key, value in kwargs.iteritems():
if value in (None, []) and key in defaults:
kwargs[key] = defaults[key]
if kwargs["push"] and self.find_uncommited_changes():
print('ERROR please commit changes before continuing')
sys.exit(1)
if not any(kwargs[item] for item in ("paths", "tests", "tags")):
kwargs["paths"], kwargs["tags"] = self.find_paths_and_tags(kwargs["verbose"])
builds, platforms, tests, talos, jobs, paths, tags, extra = self.validate_args(**kwargs)
if paths or tags:
if not os.path.exists(os.path.join(self.topobjdir, 'config.status')):
print(CONFIG_ENVIRONMENT_NOT_FOUND)
sys.exit(1)
paths = [os.path.relpath(os.path.normpath(os.path.abspath(item)), self.topsrcdir)
for item in paths]
paths_by_flavor = self.paths_by_flavor(paths=paths, tags=tags)
if not paths_by_flavor and not tests:
print("No tests were found when attempting to resolve paths:\n\n\t%s" %
paths)
sys.exit(1)
if not kwargs["intersection"]:
paths_by_flavor = self.remove_duplicates(paths_by_flavor, tests)
else:
paths_by_flavor = {}
# No point in dealing with artifacts if we aren't running any builds
local_artifact_build = False
if platforms:
local_artifact_build = kwargs.get('local_artifact_build', False)
# Add --artifact if --enable-artifact-builds is set ...
if local_artifact_build:
extra["artifact"] = True
# ... unless --no-artifact is explicitly given.
if kwargs["no_artifact"]:
if "artifact" in extra:
del extra["artifact"]
try:
msg = self.calc_try_syntax(platforms, tests, talos, jobs, builds,
paths_by_flavor, tags, extra, kwargs["intersection"])
except ValueError as e:
print(e.message)
sys.exit(1)
if local_artifact_build:
if kwargs["no_artifact"]:
print('mozconfig has --enable-artifact-builds but '
'--no-artifact specified, not including --artifact '
'flag in try syntax')
else:
print('mozconfig has --enable-artifact-builds; including '
'--artifact flag in try syntax (use --no-artifact '
'to override)')
if kwargs["verbose"] and paths_by_flavor:
print('The following tests will be selected: ')
for flavor, paths in paths_by_flavor.iteritems():
print("%s: %s" % (flavor, ",".join(paths)))
if kwargs["verbose"] or not kwargs["push"]:
print('The following try syntax was calculated:\n%s' % msg)
if kwargs["push"]:
self.push_to_try(msg, kwargs["verbose"])
if kwargs["save"] is not None:
self.save_config(kwargs["save"], msg)