Commit 381db68a authored by Takuto Ikuta's avatar Takuto Ikuta Committed by LUCI CQ

autoninja: add simple test

This is to prevent revert like https://crrev.com/c/3607513

Bug: 1317620
Change-Id: I6ab7aba5f92719bd573d22d90358f58e48aeb10c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/3607514Reviewed-by: 's avatarBruce Dawson <brucedawson@chromium.org>
Reviewed-by: 's avatarGavin Mak <gavinmak@google.com>
Commit-Queue: Takuto Ikuta <tikuta@chromium.org>
parent a3a014e9
...@@ -124,7 +124,10 @@ def CheckUnitTestsOnCommit(input_api, output_api): ...@@ -124,7 +124,10 @@ def CheckUnitTestsOnCommit(input_api, output_api):
'recipes_test.py', 'recipes_test.py',
] ]
py3_only_tests = ['ninjalog_uploader_test.py'] py3_only_tests = [
'autoninja_test.py',
'ninjalog_uploader_test.py',
]
tests = input_api.canned_checks.GetUnitTestsInDirectory( tests = input_api.canned_checks.GetUnitTestsInDirectory(
input_api, input_api,
......
...@@ -21,26 +21,28 @@ import sys ...@@ -21,26 +21,28 @@ import sys
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
# The -t tools are incompatible with -j
t_specified = False def main(args):
j_specified = False # The -t tools are incompatible with -j
offline = False t_specified = False
output_dir = '.' j_specified = False
input_args = sys.argv offline = False
# On Windows the autoninja.bat script passes along the arguments enclosed in output_dir = '.'
# double quotes. This prevents multiple levels of parsing of the special '^' input_args = args
# characters needed when compiling a single file but means that this script gets # On Windows the autoninja.bat script passes along the arguments enclosed in
# called with a single argument containing all of the actual arguments, # double quotes. This prevents multiple levels of parsing of the special '^'
# separated by spaces. When this case is detected we need to do argument # characters needed when compiling a single file but means that this script
# splitting ourselves. This means that arguments containing actual spaces are # gets called with a single argument containing all of the actual arguments,
# not supported by autoninja, but that is not a real limitation. # separated by spaces. When this case is detected we need to do argument
if (sys.platform.startswith('win') and len(sys.argv) == 2 and # splitting ourselves. This means that arguments containing actual spaces are
input_args[1].count(' ') > 0): # not supported by autoninja, but that is not a real limitation.
input_args = sys.argv[:1] + sys.argv[1].split() if (sys.platform.startswith('win') and len(args) == 2
and input_args[1].count(' ') > 0):
# Ninja uses getopt_long, which allow to intermix non-option arguments. input_args = args[:1] + args[1].split()
# To leave non supported parameters untouched, we do not use getopt.
for index, arg in enumerate(input_args[1:]): # Ninja uses getopt_long, which allow to intermix non-option arguments.
# To leave non supported parameters untouched, we do not use getopt.
for index, arg in enumerate(input_args[1:]):
if arg.startswith('-j'): if arg.startswith('-j'):
j_specified = True j_specified = True
if arg.startswith('-t'): if arg.startswith('-t'):
...@@ -58,25 +60,25 @@ for index, arg in enumerate(input_args[1:]): ...@@ -58,25 +60,25 @@ for index, arg in enumerate(input_args[1:]):
file=sys.stderr) file=sys.stderr)
print(file=sys.stderr) print(file=sys.stderr)
# Strip -o/--offline so ninja doesn't see them. # Strip -o/--offline so ninja doesn't see them.
input_args = [ arg for arg in input_args if arg not in ('-o', '--offline')] input_args = [arg for arg in input_args if arg not in ('-o', '--offline')]
use_goma = False use_goma = False
use_remoteexec = False use_remoteexec = False
# Currently get reclient binary and config dirs relative to output_dir. If # Currently get reclient binary and config dirs relative to output_dir. If
# they exist and using remoteexec, then automatically call bootstrap to start # they exist and using remoteexec, then automatically call bootstrap to start
# reproxy. This works under the current assumption that the output # reproxy. This works under the current assumption that the output
# directory is two levels up from chromium/src. # directory is two levels up from chromium/src.
reclient_bin_dir = os.path.join( reclient_bin_dir = os.path.join(output_dir, '..', '..', 'buildtools',
output_dir, '..', '..', 'buildtools', 'reclient') 'reclient')
reclient_cfg = os.path.join( reclient_cfg = os.path.join(output_dir, '..', '..', 'buildtools',
output_dir, '..', '..', 'buildtools', 'reclient_cfgs', 'reproxy.cfg') 'reclient_cfgs', 'reproxy.cfg')
# Attempt to auto-detect remote build acceleration. We support gn-based # Attempt to auto-detect remote build acceleration. We support gn-based
# builds, where we look for args.gn in the build tree, and cmake-based builds # builds, where we look for args.gn in the build tree, and cmake-based builds
# where we look for rules.ninja. # where we look for rules.ninja.
if os.path.exists(os.path.join(output_dir, 'args.gn')): if os.path.exists(os.path.join(output_dir, 'args.gn')):
with open(os.path.join(output_dir, 'args.gn')) as file_handle: with open(os.path.join(output_dir, 'args.gn')) as file_handle:
for line in file_handle: for line in file_handle:
# Either use_goma, use_remoteexec or use_rbe (in deprecation) # Either use_goma, use_remoteexec or use_rbe (in deprecation)
...@@ -96,7 +98,7 @@ if os.path.exists(os.path.join(output_dir, 'args.gn')): ...@@ -96,7 +98,7 @@ if os.path.exists(os.path.join(output_dir, 'args.gn')):
line_without_comment): line_without_comment):
use_remoteexec = True use_remoteexec = True
continue continue
else: else:
for relative_path in [ for relative_path in [
'', # GN keeps them in the root of output_dir '', # GN keeps them in the root of output_dir
'CMakeFiles' 'CMakeFiles'
...@@ -109,26 +111,29 @@ else: ...@@ -109,26 +111,29 @@ else:
use_goma = True use_goma = True
break break
# If GOMA_DISABLED is set to "true", "t", "yes", "y", or "1" (case-insensitive) # If GOMA_DISABLED is set to "true", "t", "yes", "y", or "1"
# then gomacc will use the local compiler instead of doing a goma compile. This # (case-insensitive) then gomacc will use the local compiler instead of doing
# is convenient if you want to briefly disable goma. It avoids having to rebuild # a goma compile. This is convenient if you want to briefly disable goma. It
# the world when transitioning between goma/non-goma builds. However, it is not # avoids having to rebuild the world when transitioning between goma/non-goma
# as fast as doing a "normal" non-goma build because an extra process is created # builds. However, it is not as fast as doing a "normal" non-goma build
# for each compile step. Checking this environment variable ensures that # because an extra process is created for each compile step. Checking this
# autoninja uses an appropriate -j value in this situation. # environment variable ensures that autoninja uses an appropriate -j value in
goma_disabled_env = os.environ.get('GOMA_DISABLED', '0').lower() # this situation.
if offline or goma_disabled_env in ['true', 't', 'yes', 'y', '1']: goma_disabled_env = os.environ.get('GOMA_DISABLED', '0').lower()
if offline or goma_disabled_env in ['true', 't', 'yes', 'y', '1']:
use_goma = False use_goma = False
if use_goma: if use_goma:
gomacc_file = 'gomacc.exe' if sys.platform.startswith('win') else 'gomacc' gomacc_file = 'gomacc.exe' if sys.platform.startswith('win') else 'gomacc'
goma_dir = os.environ.get('GOMA_DIR', os.path.join(SCRIPT_DIR, '.cipd_bin')) goma_dir = os.environ.get('GOMA_DIR', os.path.join(SCRIPT_DIR, '.cipd_bin'))
gomacc_path = os.path.join(goma_dir, gomacc_file) gomacc_path = os.path.join(goma_dir, gomacc_file)
# Don't invoke gomacc if it doesn't exist. # Don't invoke gomacc if it doesn't exist.
if os.path.exists(gomacc_path): if os.path.exists(gomacc_path):
# Check to make sure that goma is running. If not, don't start the build. # Check to make sure that goma is running. If not, don't start the build.
status = subprocess.call([gomacc_path, 'port'], stdout=subprocess.PIPE, status = subprocess.call([gomacc_path, 'port'],
stderr=subprocess.PIPE, shell=False) stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False)
if status == 1: if status == 1:
print('Goma is not running. Use "goma_ctl ensure_start" to start it.', print('Goma is not running. Use "goma_ctl ensure_start" to start it.',
file=sys.stderr) file=sys.stderr)
...@@ -140,34 +145,34 @@ if use_goma: ...@@ -140,34 +145,34 @@ if use_goma:
print('false') print('false')
sys.exit(1) sys.exit(1)
# Specify ninja.exe on Windows so that ninja.bat can call autoninja and not # Specify ninja.exe on Windows so that ninja.bat can call autoninja and not
# be called back. # be called back.
ninja_exe = 'ninja.exe' if sys.platform.startswith('win') else 'ninja' ninja_exe = 'ninja.exe' if sys.platform.startswith('win') else 'ninja'
ninja_exe_path = os.path.join(SCRIPT_DIR, ninja_exe) ninja_exe_path = os.path.join(SCRIPT_DIR, ninja_exe)
# A large build (with or without goma) tends to hog all system resources. # A large build (with or without goma) tends to hog all system resources.
# Launching the ninja process with 'nice' priorities improves this situation. # Launching the ninja process with 'nice' priorities improves this situation.
prefix_args = [] prefix_args = []
if (sys.platform.startswith('linux') if (sys.platform.startswith('linux')
and os.environ.get('NINJA_BUILD_IN_BACKGROUND', '0') == '1'): and os.environ.get('NINJA_BUILD_IN_BACKGROUND', '0') == '1'):
# nice -10 is process priority 10 lower than default 0 # nice -10 is process priority 10 lower than default 0
# ionice -c 3 is IO priority IDLE # ionice -c 3 is IO priority IDLE
prefix_args = ['nice'] + ['-10'] prefix_args = ['nice'] + ['-10']
# Use absolute path for ninja path,
# or fail to execute ninja if depot_tools is not in PATH.
args = prefix_args + [ninja_exe_path] + input_args[1:]
# Use absolute path for ninja path, num_cores = multiprocessing.cpu_count()
# or fail to execute ninja if depot_tools is not in PATH. if not j_specified and not t_specified:
args = prefix_args + [ninja_exe_path] + input_args[1:]
num_cores = multiprocessing.cpu_count()
if not j_specified and not t_specified:
if use_goma or use_remoteexec: if use_goma or use_remoteexec:
args.append('-j') args.append('-j')
core_multiplier = int(os.environ.get('NINJA_CORE_MULTIPLIER', '40')) core_multiplier = int(os.environ.get('NINJA_CORE_MULTIPLIER', '40'))
j_value = num_cores * core_multiplier j_value = num_cores * core_multiplier
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
# On windows, j value higher than 1000 does not improve build performance. # On windows, j value higher than 1000 does not improve build
# performance.
j_value = min(j_value, 1000) j_value = min(j_value, 1000)
elif sys.platform == 'darwin': elif sys.platform == 'darwin':
# On Mac, j value higher than 500 causes 'Too many open files' error # On Mac, j value higher than 500 causes 'Too many open files' error
...@@ -182,37 +187,41 @@ if not j_specified and not t_specified: ...@@ -182,37 +187,41 @@ if not j_specified and not t_specified:
args.append('-j') args.append('-j')
args.append('%d' % j_value) args.append('%d' % j_value)
# On Windows, fully quote the path so that the command processor doesn't think # On Windows, fully quote the path so that the command processor doesn't think
# the whole output is the command. # the whole output is the command.
# On Linux and Mac, if people put depot_tools in directories with ' ', # On Linux and Mac, if people put depot_tools in directories with ' ',
# shell would misunderstand ' ' as a path separation. # shell would misunderstand ' ' as a path separation.
# TODO(yyanagisawa): provide proper quoting for Windows. # TODO(yyanagisawa): provide proper quoting for Windows.
# see https://cs.chromium.org/chromium/src/tools/mb/mb.py # see https://cs.chromium.org/chromium/src/tools/mb/mb.py
for i in range(len(args)): for i in range(len(args)):
if (i == 0 and sys.platform.startswith('win')) or ' ' in args[i]: if (i == 0 and sys.platform.startswith('win')) or ' ' in args[i]:
args[i] = '"%s"' % args[i].replace('"', '\\"') args[i] = '"%s"' % args[i].replace('"', '\\"')
if os.environ.get('NINJA_SUMMARIZE_BUILD', '0') == '1': if os.environ.get('NINJA_SUMMARIZE_BUILD', '0') == '1':
args += ['-d', 'stats'] args += ['-d', 'stats']
# If using remoteexec and the necessary environment variables are set, # If using remoteexec and the necessary environment variables are set,
# also start reproxy (via bootstrap) before running ninja. # also start reproxy (via bootstrap) before running ninja.
if (not offline and use_remoteexec and os.path.exists(reclient_bin_dir) if (not offline and use_remoteexec and os.path.exists(reclient_bin_dir)
and os.path.exists(reclient_cfg)): and os.path.exists(reclient_cfg)):
bootstrap = os.path.join(reclient_bin_dir, 'bootstrap') bootstrap = os.path.join(reclient_bin_dir, 'bootstrap')
setup_args = [ setup_args = [
bootstrap, bootstrap, '--cfg=' + reclient_cfg,
'--cfg=' + reclient_cfg, '--re_proxy=' + os.path.join(reclient_bin_dir, 'reproxy')
'--re_proxy=' + os.path.join(reclient_bin_dir, 'reproxy')] ]
teardown_args = [bootstrap, '--cfg=' + reclient_cfg, '--shutdown'] teardown_args = [bootstrap, '--cfg=' + reclient_cfg, '--shutdown']
cmd_sep = '\n' if sys.platform.startswith('win') else '&&' cmd_sep = '\n' if sys.platform.startswith('win') else '&&'
args = setup_args + [cmd_sep] + args + [cmd_sep] + teardown_args args = setup_args + [cmd_sep] + args + [cmd_sep] + teardown_args
if offline and not sys.platform.startswith('win'): if offline and not sys.platform.startswith('win'):
# Tell goma or reclient to do local compiles. On Windows these environment # Tell goma or reclient to do local compiles. On Windows these environment
# variables are set by the wrapper batch file. # variables are set by the wrapper batch file.
print('RBE_remote_disabled=1 GOMA_DISABLED=1 ' + ' '.join(args)) return 'RBE_remote_disabled=1 GOMA_DISABLED=1 ' + ' '.join(args)
else:
print(' '.join(args)) return ' '.join(args)
if __name__ == '__main__':
print(main(sys.argv))
per-file autoninja_test.py=brucedawson@chromium.org
per-file autoninja_test.py=tikuta@chromium.org
per-file ninjalog_uploader_test.py=tikuta@chromium.org per-file ninjalog_uploader_test.py=tikuta@chromium.org
#!/usr/bin/env python3
# Copyright (c) 2022 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import multiprocessing
import os
import os.path
import sys
import unittest
import unittest.mock
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT_DIR)
import autoninja
class AutoninjaTest(unittest.TestCase):
def test_autoninja(self):
autoninja.main([])
def test_autoninja_goma(self):
with unittest.mock.patch(
'os.path.exists',
return_value=True) as mock_exists, unittest.mock.patch(
'autoninja.open', unittest.mock.mock_open(
read_data='use_goma=true')) as mock_open, unittest.mock.patch(
'subprocess.call', return_value=0):
args = autoninja.main([]).split()
mock_exists.assert_called()
mock_open.assert_called_once()
self.assertIn('-j', args)
parallel_j = int(args[args.index('-j') + 1])
self.assertGreater(parallel_j, multiprocessing.cpu_count())
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment