Skip to content

Commit

Permalink
Windows, testing: replace sh_tests with Starlark
Browse files Browse the repository at this point in the history
test_wrapper_test asserts that Bazel can run tests
with the Windows-native test wrapper, i.e. without
Bash. Yet test_wrapper_test itself required Bash
for its mock sh_test rules.

This PR replaces those sh_test rules with simple
Starlark test rules.

Related: #4319

Closes #8165.

Change-Id: I6a3d6641d0998f5a7f02def61902e3fb8187e0c9
PiperOrigin-RevId: 245688126
  • Loading branch information
laszlocsomor authored and copybara-github committed Apr 29, 2019
1 parent 9a32b86 commit 74bf788
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 80 deletions.
5 changes: 4 additions & 1 deletion src/test/py/bazel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ py_test(
"//conditions:default": ["empty_test.py"],
}),
data = select({
"//src/conditions:windows": [":printargs"],
"//src/conditions:windows": [
":printargs",
"native_test.bzl",
],
"//conditions:default": [],
}),
main = select({
Expand Down
63 changes: 63 additions & 0 deletions src/test/py/bazel/native_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2019 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.

"""For testing only.
This module exports two rules:
- bat_test is a test rule. It writes its executable (a .bat file) with
user-defined content.
- exe_test is a test rule. It copies a native executable and uses that
as its own.
"""

def _bat_test_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".bat")
ctx.actions.write(
output = out,
content = "\r\n".join(ctx.attr.content),
is_executable = True,
)
return [DefaultInfo(executable = out)]

bat_test = rule(
implementation = _bat_test_impl,
test = True,
attrs = {"content": attr.string_list()},
)

def _exe_test_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + "." + ctx.file.src.extension)
ctx.actions.run(
inputs = [ctx.file.src],
outputs = [out],
executable = "cmd.exe",
arguments = ["/C", "copy /Y %IN% %OUT%"],
env = {
"IN": ctx.file.src.path.replace("/", "\\"),
"OUT": out.path.replace("/", "\\"),
},
)
return [DefaultInfo(executable = out)]

exe_test = rule(
implementation = _exe_test_impl,
test = True,
attrs = {
"src": attr.label(
allow_single_file = True,
cfg = "host",
executable = True,
),
},
)
136 changes: 57 additions & 79 deletions src/test/py/bazel/test_wrapper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,55 +34,49 @@ def _FailWithOutput(self, output):

def _CreateMockWorkspace(self):
self.ScratchFile('WORKSPACE')
# All test targets are called <something>.bat, for the benefit of Windows.
# This makes test execution faster on Windows for the following reason:
#
# When building a sh_test rule, the main output's name is the same as the
# rule. On Unixes, this output is a symlink to the main script (the first
# entry in `srcs`), on Windows it's a copy of the file. In fact the main
# "script" does not have to be a script, it may be any executable file.
#
# On Unixes anything with the +x permission can be executed; the file's
# shebang line specifies the interpreter. On Windows, there's no such
# mechanism; Bazel runs the main script (which is typically a ".sh" file)
# through Bash. However, if the main file is a native executable, it's
# faster to run it directly than through Bash (plus it removes the need for
# Bash).
#
# Therefore on Windows, if the main script is a native executable (such as a
# ".bat" file) and has the same extension as the main file, Bazel (in case
# of sh_test) makes a copy of the file and runs it directly, rather than
# through Bash.
self.ScratchFile('foo/BUILD', [
'sh_test(',
' name = "passing_test.bat",',
' srcs = ["passing.bat"],',
'load(":native_test.bzl", "bat_test", "exe_test")',
'bat_test(',
' name = "passing_test",',
' content = ["@exit /B 0"],',
')',
'sh_test(',
' name = "failing_test.bat",',
' srcs = ["failing.bat"],',
'bat_test(',
' name = "failing_test",',
' content = ["@exit /B 1"],',
')',
'sh_test(',
' name = "printing_test.bat",',
' srcs = ["printing.bat"],',
'bat_test(',
' name = "printing_test",',
' content = [',
' "@echo lorem ipsum",',
' "@echo HOME=%HOME%",',
' "@echo TEST_SRCDIR=%TEST_SRCDIR%",',
' "@echo TEST_TMPDIR=%TEST_TMPDIR%",',
' "@echo USER=%USER%",',
' ]',
')',
'sh_test(',
' name = "runfiles_test.bat",',
' srcs = ["runfiles.bat"],',
' data = ["passing.bat"],',
'py_test(',
' name = "runfiles_test",',
' srcs = ["runfiles_test.py"],',
' data = ["dummy.dat"],',
')',
'sh_test(',
' name = "sharded_test.bat",',
' srcs = ["sharded.bat"],',
'bat_test(',
' name = "sharded_test",',
' content = [',
' "@echo STATUS=%TEST_SHARD_STATUS_FILE%",',
' "@echo INDEX=%TEST_SHARD_INDEX% TOTAL=%TEST_TOTAL_SHARDS%",',
' ],',
' shard_count = 2,',
')',
'sh_test(',
' name = "unexported_test.bat",',
' srcs = ["unexported.bat"],',
'bat_test(',
' name = "unexported_test",',
' content = [',
' "@echo GOOD=%HOME%",',
' "@echo BAD=%TEST_UNDECLARED_OUTPUTS_MANIFEST%",',
' ],',
')',
'sh_test(',
' name = "testargs_test.exe",',
' srcs = ["testargs.exe"],',
'exe_test(',
' name = "testargs_test",',
' src = "testargs.exe",',
r' args = ["foo", "a b", "", "\"c d\"", "\"\"", "bar"],',
')',
'py_test(',
Expand All @@ -104,36 +98,6 @@ def _CreateMockWorkspace(self):
' srcs = ["xml2_test.py"],',
')',
])
self.ScratchFile('foo/passing.bat', ['@exit /B 0'], executable=True)
self.ScratchFile('foo/failing.bat', ['@exit /B 1'], executable=True)
self.ScratchFile(
'foo/printing.bat', [
'@echo lorem ipsum',
'@echo HOME=%HOME%',
'@echo TEST_SRCDIR=%TEST_SRCDIR%',
'@echo TEST_TMPDIR=%TEST_TMPDIR%',
'@echo USER=%USER%',
],
executable=True)
self.ScratchFile(
'foo/runfiles.bat', [
'@echo MF=%RUNFILES_MANIFEST_FILE%',
'@echo ONLY=%RUNFILES_MANIFEST_ONLY%',
'@echo DIR=%RUNFILES_DIR%',
],
executable=True)
self.ScratchFile(
'foo/sharded.bat', [
'@echo STATUS=%TEST_SHARD_STATUS_FILE%',
'@echo INDEX=%TEST_SHARD_INDEX% TOTAL=%TEST_TOTAL_SHARDS%',
],
executable=True)
self.ScratchFile(
'foo/unexported.bat', [
'@echo GOOD=%HOME%',
'@echo BAD=%TEST_UNDECLARED_OUTPUTS_MANIFEST%',
],
executable=True)

self.CopyFile(
src_path=self.Rlocation('io_bazel/src/test/py/bazel/printargs.exe'),
Expand Down Expand Up @@ -172,6 +136,20 @@ def _CreateMockWorkspace(self):
with open(dat_file_path, 'wb') as f:
f.write(dat_file)

self.CopyFile(
src_path=self.Rlocation('io_bazel/src/test/py/bazel/native_test.bzl'),
dst_path='foo/native_test.bzl')

self.ScratchFile(
'foo/runfiles_test.py', [
'from __future__ import print_function',
'import os',
'print("MF=%s" % os.environ.get("RUNFILES_MANIFEST_FILE"))',
'print("ONLY=%s" % os.environ.get("RUNFILES_MANIFEST_ONLY"))',
'print("DIR=%s" % os.environ.get("RUNFILES_DIR"))',
],
executable=True)

self.ScratchFile(
'foo/undecl_test.py', [
'from bazel_tools.tools.python.runfiles import runfiles',
Expand Down Expand Up @@ -235,7 +213,7 @@ def _CreateMockWorkspace(self):
def _AssertPassingTest(self, flag):
exit_code, _, stderr = self.RunBazel([
'test',
'//foo:passing_test.bat',
'//foo:passing_test',
'-t-',
flag,
])
Expand All @@ -244,7 +222,7 @@ def _AssertPassingTest(self, flag):
def _AssertFailingTest(self, flag):
exit_code, _, stderr = self.RunBazel([
'test',
'//foo:failing_test.bat',
'//foo:failing_test',
'-t-',
flag,
])
Expand All @@ -253,7 +231,7 @@ def _AssertFailingTest(self, flag):
def _AssertPrintingTest(self, flag):
exit_code, stdout, stderr = self.RunBazel([
'test',
'//foo:printing_test.bat',
'//foo:printing_test',
'-t-',
'--test_output=all',
flag,
Expand Down Expand Up @@ -293,7 +271,7 @@ def _AssertPrintingTest(self, flag):
def _AssertRunfiles(self, flag):
exit_code, stdout, stderr = self.RunBazel([
'test',
'//foo:runfiles_test.bat',
'//foo:runfiles_test',
'-t-',
'--test_output=all',
# Ensure Bazel does not create a runfiles tree.
Expand All @@ -318,7 +296,7 @@ def _AssertRunfiles(self, flag):
mf_contents = TestWrapperTest._ReadFile(mf)
# Assert that the data dependency is listed in the runfiles manifest.
if not any(
line.split(' ', 1)[0].endswith('foo/passing.bat')
line.split(' ', 1)[0].endswith('foo/dummy.dat')
for line in mf_contents):
self._FailWithOutput(mf_contents)

Expand All @@ -328,7 +306,7 @@ def _AssertRunfiles(self, flag):
def _AssertShardedTest(self, flag):
exit_code, stdout, stderr = self.RunBazel([
'test',
'//foo:sharded_test.bat',
'//foo:sharded_test',
'-t-',
'--test_output=all',
flag,
Expand All @@ -353,7 +331,7 @@ def _AssertShardedTest(self, flag):
def _AssertUnexportsEnvvars(self, flag):
exit_code, stdout, stderr = self.RunBazel([
'test',
'//foo:unexported_test.bat',
'//foo:unexported_test',
'-t-',
'--test_output=all',
flag,
Expand All @@ -379,7 +357,7 @@ def _AssertTestArgs(self, flag):
# to test for future (as of 2019-04-05) behavior.
'--incompatible_windows_style_arg_escaping',
'test',
'//foo:testargs_test.exe',
'//foo:testargs_test',
'-t-',
'--test_output=all',
'--test_arg=baz',
Expand Down

0 comments on commit 74bf788

Please sign in to comment.