--- a/taskcluster/actions/hello-action.py
+++ b/taskcluster/actions/hello-action.py
@@ -23,8 +23,9 @@ But you can also use the default value `
""".strip(),
},
)
def hello_world_action(parameters, input, task_group_id, task_id, task):
print "This message was triggered from context-menu of taskId: {}".format(task_id)
print ""
print "Hello {}".format(input)
print "--- Action is now executed"
+
new file mode 100644
--- /dev/null
+++ b/taskcluster/actions/mochitest-retrigger-action.py
@@ -0,0 +1,92 @@
+import copy
+import logging
+
+import requests
+from slugid import nice as slugid
+
+from .registry import register_callback_action
+from taskgraph.create import create_task
+from taskgraph.util.time import (
+ current_json_time,
+ json_time_from_now
+)
+
+TASKCLUSTER_QUEUE_URL = "https://queue.taskcluster.net/v1/task"
+
+logger = logging.getLogger(__name__)
+
+
+@register_callback_action(
+ title='Schedule mochitest retrigger',
+ symbol='mdr',
+ description="Retriggers the specified mochitest job with additional options",
+ order=10000, # Put this at the very bottom/end of any menu (default)
+ context=[{}], # Applies to any task
+ available=lambda parameters: True, # available regardless decision parameters (default)
+ schema={
+ 'type': 'object',
+ 'properties': {
+ 'path': {
+ 'type': 'string',
+ 'maxLength': 255,
+ 'default': '',
+ 'title': 'Path name',
+ 'description': 'Path of mochitest to retrigger'
+ },
+ 'logLevel': {
+ 'type': 'string',
+ 'enum': ['debug', 'info', 'warning', 'error', 'critical'],
+ 'default': 'debug',
+ 'title': 'Log level',
+ 'description': 'Log level for output (default is DEBUG, which is highest)'
+ },
+ 'runUntilFail': {
+ 'type': 'boolean',
+ 'default': False,
+ 'title': 'Run until failure',
+ 'description': 'Runs the specified set of tests repeatedly until failure (or 30 times)'
+ },
+ 'environment': {
+ 'type': 'object',
+ 'default': {'MY_ENV_1': 'myvalue1'},
+ 'title': 'Extra environment variables',
+ 'description': 'Extra environment variables to use for this run'
+ }
+ }
+ }
+)
+def mochitest_retrigger_action(parameters, input, task_group_id, task_id, task):
+ logger.info("Fetching task definition for mochitest task id %s...", task_id)
+ task_definition = requests.get(url="{}/{}".format(TASKCLUSTER_QUEUE_URL,
+ task_id)).json()
+ new_task_definition = copy.copy(task_definition)
+
+ # set new created, deadline, and expiry fields
+ new_task_definition['created'] = current_json_time()
+ new_task_definition['deadline'] = json_time_from_now('1d')
+ new_task_definition['expires'] = json_time_from_now('30d')
+
+ # don't want to run mozharness tests
+ new_task_definition['payload']['command'] += ['--no-run-tests']
+
+ custom_mach_command = ['mochitest']
+ custom_mach_command += ['--log-mach=-',
+ '--log-mach-level={}'.format(input['logLevel'])]
+ if input['runUntilFail']:
+ custom_mach_command += ['--run-until-fail']
+ custom_mach_command += [input['path']]
+ new_task_definition['payload']['env']['CUSTOM_MACH_COMMAND'] = ' '.join(
+ custom_mach_command)
+
+ # update environment
+ new_task_definition['payload']['env'].update(input['environment'])
+
+ # tweak the treeherder symbol
+ new_task_definition['extra']['treeherder']['symbol'] = 'custom'
+
+ print new_task_definition
+ # actually create the new task
+ new_task_id = slugid()
+ logger.info("Creating new mochitest task with id %s", new_task_id)
+ session = requests.Session()
+ create_task(session, new_task_id, 'mochitest-debug', new_task_definition)
--- a/taskcluster/actions/registry.py
+++ b/taskcluster/actions/registry.py
@@ -219,29 +219,17 @@ def register_callback_action(title, symb
'image': docker_image('decision'),
'maxRunTime': 1800,
'command': [
'/home/worker/bin/run-task', '--vcs-checkout=/home/worker/checkouts/gecko',
'--', 'bash', '-cx',
"""\
cd /home/worker/checkouts/gecko &&
ln -s /home/worker/artifacts artifacts &&
-./mach --log-no-times taskgraph action-callback """ + ' '.join([
- "--pushlog-id='{}'".format(parameters['pushlog_id']),
- "--pushdate='{}'".format(parameters['pushdate']),
- "--project='{}'".format(parameters['project']),
- "--message='{}'".format(parameters['message'].replace("'", "'\\''")),
- "--owner='{}'".format(parameters['owner']),
- "--level='{}'".format(parameters['level']),
- "--base-repository='https://hg.mozilla.org/mozilla-central'",
- "--head-repository='{}'".format(parameters['head_repository']),
- "--head-ref='{}'".format(parameters['head_ref']),
- "--head-rev='{}'".format(parameters['head_rev']),
- "--revision-hash='{}'\n".format(parameters['head_rev']),
- ]),
+./mach --log-no-times taskgraph action-callback"""
],
},
'extra': {
'treeherder': {
'groupName': 'action-callback',
'groupSymbol': 'AC',
'symbol': symbol,
},
@@ -294,17 +282,17 @@ def trigger_action_callback():
Trigger action callback using arguments from environment variables.
"""
global callbacks
task_group_id = os.environ.get('ACTION_TASK_GROUP_ID', None)
task_id = json.loads(os.environ.get('ACTION_TASK_ID', 'null'))
task = json.loads(os.environ.get('ACTION_TASK', 'null'))
input = json.loads(os.environ.get('ACTION_INPUT', 'null'))
callback = os.environ.get('ACTION_CALLBACK', None)
- parameters = json.loads(os.environ.get('ACTION_PARAMETERS', 'null'))
+ parameters = json.loads(os.environ.get('ACTION_PARAMETERS', 'null')) or {}
cb = callbacks.get(callback, None)
if not cb:
raise Exception('Unknown callback: {}'.format(callback))
cb(Parameters(**parameters), input, task_group_id, task_id, task)
# Load all modules from this folder, relying on the side-effects of register_
# functions to populate the action registry.
--- a/taskcluster/ci/test/tests.yml
+++ b/taskcluster/ci/test/tests.yml
@@ -1,9 +1,10 @@
# Each stanza here describes a particular test suite or sub-suite. These are
+
# processed through the transformations described in kind.yml to produce a
# bunch of tasks. See the schema in `test-descriptions.py` for a description
# of the fields used here.
# Note that these are in lexical order
cppunit:
description: "CPP Unit Tests"
--- a/taskcluster/mach_commands.py
+++ b/taskcluster/mach_commands.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
+
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
from __future__ import absolute_import, print_function, unicode_literals
import json
@@ -301,17 +302,22 @@ class MachCommands(MachCommandBase):
except Exception:
traceback.print_exc()
sys.exit(1)
@SubCommand('taskgraph', 'action-callback',
description='Run action callback used by action tasks')
def action_callback(self, **options):
import actions
- actions.trigger_action_callback()
+ try:
+ self.setup_logging()
+ return actions.trigger_action_callback()
+ except Exception:
+ traceback.print_exc()
+ sys.exit(1)
def setup_logging(self, quiet=False, verbose=True):
"""
Set up Python logging for all loggers, sending results to stderr (so
that command output can be redirected easily) and adding the typical
mach timestamp.
"""
# remove the old terminal handler
--- a/taskcluster/scripts/tester/test-ubuntu.sh
+++ b/taskcluster/scripts/tester/test-ubuntu.sh
@@ -181,8 +181,14 @@ exec \${cmd}" > ${mozharness_bin}
chmod +x ${mozharness_bin}
# In interactive mode, the user will be prompted with options for what to do.
if ! $TASKCLUSTER_INTERACTIVE; then
# run the given mozharness script and configs, but pass the rest of the
# arguments in from our own invocation
${mozharness_bin};
fi
+
+# Run a custom mach command (this is typically used by action tasks to run
+# harnesses in a particular way)
+if [ "$CUSTOM_MACH_COMMAND" ]; then
+ eval "/home/worker/workspace/build/tests/mach ${CUSTOM_MACH_COMMAND}"
+fi
--- a/testing/mozharness/scripts/desktop_unittest.py
+++ b/testing/mozharness/scripts/desktop_unittest.py
@@ -145,16 +145,29 @@ class DesktopUnittest(TestingMixin, Merc
"help": "Number of this chunk"}
],
[["--allow-software-gl-layers"], {
"action": "store_true",
"dest": "allow_software_gl_layers",
"default": False,
"help": "Permits a software GL implementation (such as LLVMPipe) to use the GL compositor."}
],
+ [["--test-path"], {
+ "action": "extend",
+ "dest": "test_path",
+ "type": "string",
+ "help": "Specify which test path to run (in lieu of chunking)"
+ }
+ ],
+ [["--run-until-fail"], {
+ "action": "store_true",
+ "dest": "run_until_fail",
+ "default": False,
+ "help": "Passes the --run-until-fail option to the mochitest harness"}
+ ],
] + copy.deepcopy(testing_config_options) + \
copy.deepcopy(blobupload_config_options) + \
copy.deepcopy(code_coverage_config_options)
def __init__(self, require_config_file=True):
# abs_dirs defined already in BaseScript but is here to make pylint happy
self.abs_dirs = None
super(DesktopUnittest, self).__init__(
@@ -410,24 +423,33 @@ class DesktopUnittest(TestingMixin, Merc
option = option % str_format_values
if not option.endswith('None'):
base_cmd.append(option)
if self.structured_output(
suite_category,
self._query_try_flavor(suite_category, suite)
):
base_cmd.append("--log-raw=-")
- return base_cmd
else:
self.warning("Suite options for %s could not be determined."
"\nIf you meant to have options for this suite, "
"please make sure they are specified in your "
"config under %s_options" %
(suite_category, suite_category))
+ if c.get('run_until_fail'):
+ base_cmd.extend(['--run-until-fail'])
+
+ if c.get('log_level'):
+ base_cmd += ['--console-level', c['log_level'].upper()]
+
+ # Specify test path last
+ if c.get('test_path'):
+ base_cmd.extend(c['test_path'])
+
return base_cmd
else:
self.fatal("'binary_path' could not be determined.\n This should "
"be like '/path/build/application/firefox/firefox'"
"\nIf you are running this script without the 'install' "
"action (where binary_path is set), please ensure you are"
" either:\n(1) specifying it in the config file under "
"binary_path\n(2) specifying it on command line with the"