blob: 3f859f2b5ed80d9c75fb5cf9594413a3d3385218 [file] [log] [blame]
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import unittest
from src.test.py.bazel import test_base
class ActionTempTest(test_base.TestBase):
"""Test that Bazel sets a TMP/TEMP/TMPDIR envvar for actions."""
_invalidations = 0
def testActionTemp(self):
self._CreateWorkspace()
strategies = self._SpawnStrategies()
self.assertIn('standalone', strategies)
if not test_base.TestBase.IsWindows():
self.assertIn('sandboxed', strategies)
self.assertIn('processwrapper-sandbox', strategies)
bazel_bin = self._BazelOutputDirectory('bazel-bin')
bazel_genfiles = self._BazelOutputDirectory('bazel-genfiles')
# Test without user-defined temp directory.
# In the absence of TMP/TEMP/TMPDIR, the LocalEnvProvider implementations
# set the fallback temp directory.
if test_base.TestBase.IsWindows():
expected_tmpdir_regex = r'execroot\\.+\\local-spawn-runner.[0-9]+\\work$'
else:
expected_tmpdir_regex = '^/tmp$'
self._AssertTempDir('standalone', expected_tmpdir_regex, bazel_bin,
bazel_genfiles)
if not test_base.TestBase.IsWindows():
self._AssertTempDir('sandboxed', expected_tmpdir_regex, bazel_bin,
bazel_genfiles)
self._AssertTempDir('processwrapper-sandbox', expected_tmpdir_regex,
bazel_bin, bazel_genfiles)
# Test with user-defined temp directory.
self._AssertClientEnvTemp('standalone', bazel_bin, bazel_genfiles)
if not test_base.TestBase.IsWindows():
self._AssertClientEnvTemp('sandboxed', bazel_bin, bazel_genfiles)
self._AssertClientEnvTemp('processwrapper-sandbox', bazel_bin,
bazel_genfiles)
# Helper methods start here -------------------------------------------------
def _AssertClientEnvTemp(self, strategy, bazel_bin, bazel_genfiles):
def _Impl(tmp_dir):
self._AssertTempDir(
strategy=strategy,
expected_tmpdir_regex=os.path.basename(tmp_dir),
bazel_bin=bazel_bin,
bazel_genfiles=bazel_genfiles,
env_add=dict((k, tmp_dir) for k in self._TempEnvvars()))
_Impl(self.ScratchDir(strategy + '-temp-1'))
# Assert that the actions pick up the current client environment.
# Check this by invalidating the actions (update input.txt) and running
# Bazel with a different environment.
_Impl(self.ScratchDir(strategy + '-temp-2'))
def _AssertTempDir(self,
strategy,
expected_tmpdir_regex,
bazel_bin,
bazel_genfiles,
env_add=None):
self._invalidations += 1
input_file_contents = str(self._invalidations)
self._UpdateInputFile(input_file_contents)
outputs = self._BuildRules(
strategy,
bazel_bin,
bazel_genfiles,
env_remove=self._TempEnvvars(),
env_add=env_add)
self.assertEqual(len(outputs), 2)
self._AssertOutputFileContents(outputs['genrule'], input_file_contents,
expected_tmpdir_regex)
self._AssertOutputFileContents(outputs['skylark'], input_file_contents,
expected_tmpdir_regex)
def _UpdateInputFile(self, content):
self.ScratchFile('foo/input.txt', [content])
def _TempEnvvars(self):
if test_base.TestBase.IsWindows():
return ['TMP', 'TEMP']
else:
return ['TMPDIR']
def _BazelOutputDirectory(self, info_key):
exit_code, stdout, stderr = self.RunBazel(['info', info_key])
self.AssertExitCode(exit_code, 0, stderr)
return stdout[0]
def _InvalidateActions(self, content):
self.ScratchFile('foo/input.txt', [content])
def _CreateWorkspace(self, build_flags=None):
if test_base.TestBase.IsWindows():
toolname = 'foo.cmd'
toolsrc = [
'@SETLOCAL ENABLEEXTENSIONS',
'@echo ON',
'if [%TMP%] == [] exit /B 1',
'if [%TEMP%] == [] exit /B 1',
'if not exist "%2" exit /B 2',
'set input_file=%2',
# TMP/TEMP may refer to directories that other processes are also
# writing to, so let's not try to create any files there because we
# cannot generate safe temp file names. Instead just check that the
# directory exists. It'd be nice to check that the directory is
# writable, but I (@laszlocsomor) don't know how to do that without
# actually attempting to write to the directory.
'type "%input_file:/=\\%" > "%1"',
'if exist "%TMP%" (echo TMP:y >> "%1") else (echo TMP:n >> "%1")',
'if exist "%TEMP%" (echo TEMP:y >> "%1") else (echo TEMP:n >> "%1")',
'set TMP >> "%1"',
'set TEMP >> "%1"',
'exit /B 0',
]
else:
toolname = 'foo.sh'
toolsrc = [
'#!/bin/bash',
'set -eu',
'if [ -n "${TMPDIR:-}" ]; then',
' sleep 1',
' cat "$2" > "$1"',
# TMPDIR might be "/tmp" or other shared directory, so we need a
# unique name for the temp file we want to create there.
' tmpfile="$(mktemp "$TMPDIR/tmp.XXXXXXXX")"',
' echo foo > "$tmpfile"',
' cat "$tmpfile" >> "$1"',
' rm "$tmpfile"',
' echo "TMPDIR=${TMPDIR}" >> "$1"',
'else',
' exit 1',
'fi',
]
self.ScratchFile('WORKSPACE')
self.ScratchFile('foo/' + toolname, toolsrc, executable=True)
self.ScratchFile('foo/foo.bzl', [
'def _impl(ctx):',
' ctx.actions.run(',
' executable=ctx.executable.tool,',
' arguments=[ctx.outputs.out.path, ctx.file.src.path],',
' inputs=[ctx.file.src],',
' outputs=[ctx.outputs.out])',
' return [DefaultInfo(files=depset([ctx.outputs.out]))]',
'',
'foorule = rule(',
' implementation=_impl,',
' attrs={"tool": attr.label(executable=True, cfg="host",',
' allow_single_file=True),',
' "src": attr.label(allow_single_file=True)},',
' outputs={"out": "%{name}.txt"},',
')',
])
self.ScratchFile('foo/BUILD', [
'load("//foo:foo.bzl", "foorule")',
'',
'genrule(',
' name = "genrule",',
' tools = ["%s"],' % toolname,
' srcs = ["input.txt"],',
' outs = ["genrule.txt"],',
' cmd = "$(location %s) $@ $(location input.txt)",' % toolname,
')',
'',
'foorule(',
' name = "skylark",',
' src = "input.txt",',
' tool = "%s",' % toolname,
')',
])
def _SpawnStrategies(self):
"""Returns the list of supported --spawn_strategy values."""
exit_code, _, stderr = self.RunBazel([
'build', '--color=no', '--curses=no', '--spawn_strategy=foo'
])
self.AssertExitCode(exit_code, 2, stderr)
pattern = re.compile(
r'^ERROR:.*is an invalid value for.*Valid values are: (.*)$')
for line in stderr:
m = pattern.match(line)
if m:
return set(e.strip() for e in m.groups()[0].split(','))
return []
def _BuildRules(self,
strategy,
bazel_bin,
bazel_genfiles,
env_remove=None,
env_add=None):
def _ReadFile(path):
with open(path, 'rt') as f:
return [l.strip() for l in f]
exit_code, _, stderr = self.RunBazel([
'build',
'--verbose_failures',
'--spawn_strategy=%s' % strategy,
'//foo:genrule',
'//foo:skylark',
], env_remove, env_add)
self.AssertExitCode(exit_code, 0, stderr)
self.assertTrue(
os.path.exists(os.path.join(bazel_genfiles, 'foo/genrule.txt')))
self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'foo/skylark.txt')))
return {
'genrule': _ReadFile(os.path.join(bazel_genfiles, 'foo/genrule.txt')),
'skylark': _ReadFile(os.path.join(bazel_bin, 'foo/skylark.txt'))
}
def _AssertOutputFileContents(self, lines, input_file_line,
expected_tmpdir_regex):
if test_base.TestBase.IsWindows():
# 5 lines = input_file_line, TMP:y, TEMP:y, TMP=<path>, TEMP=<path>
if len(lines) != 5:
self.fail('lines=%s' % lines)
self.assertEqual(lines[0:3], [input_file_line, 'TMP:y', 'TEMP:y'])
tmp = lines[3].split('=', 1)[1]
temp = lines[4].split('=', 1)[1]
self.assertRegexpMatches(tmp, expected_tmpdir_regex)
self.assertEqual(tmp, temp)
else:
# 3 lines = input_file_line, foo, TMPDIR
if len(lines) != 3:
self.fail('lines=%s' % lines)
self.assertEqual(lines[0:2], [input_file_line, 'foo'])
tmpdir = lines[2].split('=', 1)[1]
self.assertRegexpMatches(tmpdir, expected_tmpdir_regex)
if __name__ == '__main__':
unittest.main()