Index: llvm/utils/lit/lit/TestRunner.py =================================================================== --- llvm/utils/lit/lit/TestRunner.py +++ llvm/utils/lit/lit/TestRunner.py @@ -17,7 +17,7 @@ except ImportError: from io import StringIO -from lit.ShCommands import GlobItem +from lit.ShCommands import GlobItem, Command import lit.ShUtil as ShUtil import lit.Test as Test import lit.util @@ -627,19 +627,34 @@ # Reference the global environment by default. cmd_shenv = shenv args = list(j.args) - while args[0] == 'env': - # Create a copy of the global environment and modify it for this one - # command. There might be multiple envs in a pipeline, and - # there might be multiple envs in a command (usually when one comes - # from a substitution): - # env FOO=1 llc < %s | env BAR=2 llvm-mc | FileCheck %s - # env FOO=1 %{another_env_plus_cmd} | FileCheck %s - if cmd_shenv is shenv: - cmd_shenv = ShellEnvironment(shenv.cwd, shenv.env) - args = updateEnv(cmd_shenv, args) - if not args: - raise InternalShellError(j, - "Error: 'env' requires a subcommand") + not_args = [] + not_count = 0 + not_crash = False + while True: + if args[0] == 'env': + # Create a copy of the global environment and modify it for + # this one command. There might be multiple envs in a pipeline, + # and there might be multiple envs in a command (usually when + # one comes from a substitution): + # env FOO=1 llc < %s | env BAR=2 llvm-mc | FileCheck %s + # env FOO=1 %{another_env_plus_cmd} | FileCheck %s + if cmd_shenv is shenv: + cmd_shenv = ShellEnvironment(shenv.cwd, shenv.env) + args = updateEnv(cmd_shenv, args) + if not args: + raise InternalShellError(j, "Error: 'env' requires a" + " subcommand") + elif args[0] == 'not': + not_args.append(args.pop(0)) + not_count += 1 + if args and args[0] == '--crash': + not_args.append(args.pop(0)) + not_crash = True + if not args: + raise InternalShellError(j, "Error: 'not' requires a" + " subcommand") + else: + break # Handle in-process builtins. # @@ -655,13 +670,37 @@ if not cmd_shenv is shenv: raise InternalShellError(j, "Error: 'env' cannot call '{}'" .format(args[0])) + if not_crash: + raise InternalShellError(j, "Error: 'not --crash' cannot call" + " '{}'".format(args[0])) if len(cmd.commands) != 1: raise InternalShellError(j, "Unsupported: '{}' cannot be part" " of a pipeline".format(args[0])) - result = inproc_builtin(j, cmd_shenv) + result = inproc_builtin(Command(args, j.redirects), cmd_shenv) + if not_count % 2: + result.exitCode = int(not result.exitCode) + result.command.args = j.args; results.append(result) return result.exitCode + # Resolve any out-of-process builtin command before adding back 'not' + # commands. + if args[0] in builtin_commands: + args.insert(0, sys.executable) + cmd_shenv.env['PYTHONPATH'] = \ + os.path.dirname(os.path.abspath(__file__)) + args[1] = os.path.join(builtin_commands_dir, args[1] + ".py") + + # We had to search through the 'not' commands to find all the 'env' + # commands and any other in-process builtin command. We don't want to + # reimplement 'not' and its '--crash' here, so just push all 'not' + # commands back to be called as external commands. Because this + # approach effectively moves all 'env' commands up front, it relies on + # the assumptions that (1) environment variables are not intended to be + # relevant to 'not' commands and (2) the 'env' command should always + # blindly pass along the status it receives from any command it calls. + args = not_args + args + stdin, stdout, stderr = processRedirects(j, default_stdin, cmd_shenv, opened_files) @@ -683,17 +722,15 @@ # Resolve the executable path ourselves. executable = None - is_builtin_cmd = args[0] in builtin_commands; - if not is_builtin_cmd: - # For paths relative to cwd, use the cwd of the shell environment. - if args[0].startswith('.'): - exe_in_cwd = os.path.join(cmd_shenv.cwd, args[0]) - if os.path.isfile(exe_in_cwd): - executable = exe_in_cwd - if not executable: - executable = lit.util.which(args[0], cmd_shenv.env['PATH']) - if not executable: - raise InternalShellError(j, '%r: command not found' % args[0]) + # For paths relative to cwd, use the cwd of the shell environment. + if args[0].startswith('.'): + exe_in_cwd = os.path.join(cmd_shenv.cwd, args[0]) + if os.path.isfile(exe_in_cwd): + executable = exe_in_cwd + if not executable: + executable = lit.util.which(args[0], cmd_shenv.env['PATH']) + if not executable: + raise InternalShellError(j, '%r: command not found' % args[0]) # Replace uses of /dev/null with temporary files. if kAvoidDevNull: @@ -712,11 +749,6 @@ # Expand all glob expressions args = expand_glob_expressions(args, cmd_shenv.cwd) - if is_builtin_cmd: - args.insert(0, sys.executable) - cmd_shenv.env['PYTHONPATH'] = \ - os.path.dirname(os.path.abspath(__file__)) - args[1] = os.path.join(builtin_commands_dir ,args[1] + ".py") # On Windows, do our own command line quoting for better compatibility # with some core utility distributions. Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-not-builtin.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-not-builtin.txt @@ -0,0 +1,4 @@ +# Other tests thoroughly check that 'env' cannot call various builtin commands. +# Pick one and make sure it fails even if there's a 'not' in the way. + +# RUN: env -u FOO BAR=3 not rm %t.no-such-file Index: llvm/utils/lit/tests/Inputs/shtest-not/fail.py =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/fail.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import print_environment +import sys + +print_environment.execute() +sys.exit(1) Index: llvm/utils/lit/tests/Inputs/shtest-not/lit.cfg =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/lit.cfg @@ -0,0 +1,7 @@ +import lit.formats +config.name = 'shtest-not' +config.suffixes = ['.txt'] +config.test_format = lit.formats.ShTest() +config.test_source_root = None +config.test_exec_root = None +config.substitutions.append(('%{python}', '"%s"' % (sys.executable))) Index: llvm/utils/lit/tests/Inputs/shtest-not/not-args-last-is-crash.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-args-last-is-crash.txt @@ -0,0 +1 @@ +# RUN: not --crash Index: llvm/utils/lit/tests/Inputs/shtest-not/not-args-nested-none.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-args-nested-none.txt @@ -0,0 +1 @@ +# RUN: not not not Index: llvm/utils/lit/tests/Inputs/shtest-not/not-args-none.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-args-none.txt @@ -0,0 +1 @@ +# RUN: not Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-cd.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-cd.txt @@ -0,0 +1,3 @@ +# Internal cd always succeeds. +# RUN: not not cd foobar +# RUN: not --crash cd foobar Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-colon.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-colon.txt @@ -0,0 +1,3 @@ +# ":" always succeeds. +# RUN: not not : foobar +# RUN: not --crash : Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-diff-with-crash.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-diff-with-crash.txt @@ -0,0 +1,6 @@ +# Lit's diff is out-of-process, so check that "not --crash diff" fails because +# diff doesn't crash rather than because "not --crash diff" isn't supported. + +# RUN: echo 'foo' > %t.foo +# RUN: echo 'bar' > %t.bar +# RUN: not --crash diff -u %t.foo %t.bar Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-diff.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-diff.txt @@ -0,0 +1,16 @@ +# RUN: echo 'foo' > %t.foo0 +# RUN: echo 'foo' > %t.foo1 +# RUN: echo 'bar' > %t.bar + +# diff fails. +# RUN: not diff %t.foo0 %t.bar +# RUN: not not not diff %t.foo0 %t.bar +# RUN: not not not not not diff %t.foo0 %t.bar + +# diff succeeds. +# RUN: diff %t.foo0 %t.foo1 +# RUN: not not diff %t.foo0 %t.foo1 +# RUN: not not not not diff %t.foo0 %t.foo1 + +# diff succeeds but we expect failure. +# RUN: not diff %t.foo0 %t.foo1 Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-echo.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-echo.txt @@ -0,0 +1,3 @@ +# Internal "echo" always succeeds. +# RUN: not not echo hello world +# RUN: not --crash echo hello world Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-env-builtin.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-env-builtin.txt @@ -0,0 +1,4 @@ +# Other tests thoroughly check that 'not' cannot call various builtin commands. +# Pick one and make sure it fails even if there's an 'env' in the way. + +# RUN: not --crash env -u FOO BAR=3 rm %t.no-such-file Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-export.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-export.txt @@ -0,0 +1,3 @@ +# Internal "export" always succeeds. +# RUN: not not export FOO=1 +# RUN: not --crash export BAZ=3 Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-external.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-external.txt @@ -0,0 +1,66 @@ +# RUN: echo foo > %t.in +# RUN: echo bar >> %t.in + + +# Simple uses of 'not' + +# RUN: not %{python} fail.py +# RUN: not not %{python} pass.py +# RUN: not not not %{python} fail.py +# RUN: not not not not %{python} pass.py + + +# Simple uses of 'not --crash' + +# RUN: not not --crash %{python} pass.py +# RUN: not not --crash %{python} fail.py +# RUN: not not --crash not %{python} pass.py +# RUN: not not --crash not %{python} fail.py + + +# Various patterns of 'not' and 'env' +# +# There's no particular pattern to the 'env' arguments except we try not to +# do the same thing every time. + +# RUN: env not %{python} fail.py | FileCheck -check-prefixes=FOO-NO,BAR-NO %s +# RUN: not env %{python} fail.py | FileCheck -check-prefixes=FOO-NO,BAR-NO %s + +# RUN: env FOO=1 not %{python} fail.py \ +# RUN: | FileCheck -check-prefixes=FOO1,BAR-NO %s + +# RUN: not env FOO=1 BAR=1 %{python} fail.py \ +# RUN: | FileCheck -check-prefixes=FOO1,BAR1 %s + +# RUN: env FOO=1 BAR=1 not env -u FOO BAR=2 %{python} fail.py \ +# RUN: | FileCheck -check-prefixes=FOO-NO,BAR2 %s + +# RUN: not env FOO=1 BAR=1 not env -u FOO -u BAR %{python} pass.py \ +# RUN: | FileCheck -check-prefixes=FOO-NO,BAR-NO %s + +# RUN: not not env FOO=1 env FOO=2 BAR=1 %{python} pass.py \ +# RUN: | FileCheck -check-prefixes=FOO2,BAR1 %s + +# RUN: env FOO=1 -u BAR env -u FOO BAR=1 not not %{python} pass.py \ +# RUN: | FileCheck -check-prefixes=FOO-NO,BAR1 %s + + +# Various patterns of 'not', 'not --crash', and 'env' + +# RUN: not env FOO=1 BAR=1 env FOO=2 BAR=2 not --crash %{python} pass.py \ +# RUN: | FileCheck -check-prefixes=FOO2,BAR2 %s + +# RUN: not env FOO=1 BAR=1 not --crash not %{python} pass.py \ +# RUN: | FileCheck -check-prefixes=FOO1,BAR1 %s + +# RUN: not not --crash env -u BAR not env -u FOO BAR=1 %{python} pass.py \ +# RUN: | FileCheck -check-prefixes=FOO-NO,BAR1 %s + + +# FOO-NO: FOO = [undefined] +# FOO1: FOO = 1 +# FOO2: FOO = 2 + +# BAR-NO: BAR = [undefined] +# BAR1: BAR = 1 +# BAR2: BAR = 2 Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-mkdir.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-mkdir.txt @@ -0,0 +1,2 @@ +# RUN: not mkdir %t/foobar +# RUN: not --crash mkdir foobar Index: llvm/utils/lit/tests/Inputs/shtest-not/not-calls-rm.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/not-calls-rm.txt @@ -0,0 +1,2 @@ +# RUN: not rm %t +# RUN: not --crash rm foobar Index: llvm/utils/lit/tests/Inputs/shtest-not/pass.py =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/pass.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +import print_environment + +print_environment.execute() Index: llvm/utils/lit/tests/Inputs/shtest-not/print_environment.py =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-not/print_environment.py @@ -0,0 +1,6 @@ +from __future__ import print_function +import os + +def execute(): + for name in ['FOO', 'BAR']: + print(name, '=', os.environ.get(name, '[undefined]')) Index: llvm/utils/lit/tests/shtest-env.py =================================================================== --- llvm/utils/lit/tests/shtest-env.py +++ llvm/utils/lit/tests/shtest-env.py @@ -7,7 +7,7 @@ # Make sure env commands are included in printed commands. -# CHECK: -- Testing: 15 tests{{.*}} +# CHECK: -- Testing: 16 tests{{.*}} # CHECK: FAIL: shtest-env :: env-args-last-is-assign.txt ({{[^)]*}}) # CHECK: $ "env" "FOO=1" @@ -67,6 +67,11 @@ # CHECK: Error: 'env' cannot call 'mkdir' # CHECK: error: command failed with exit status: {{.*}} +# CHECK: FAIL: shtest-env :: env-calls-not-builtin.txt ({{[^)]*}}) +# CHECK: $ "env" "-u" "FOO" "BAR=3" "not" "rm" "{{.*}}.no-such-file" +# CHECK: Error: 'env' cannot call 'rm' +# CHECK: error: command failed with exit status: {{.*}} + # CHECK: FAIL: shtest-env :: env-calls-rm.txt ({{[^)]*}}) # CHECK: $ "env" "-u" "FOO" "BAR=3" "rm" "foobar" # CHECK: Error: 'env' cannot call 'rm' @@ -89,5 +94,5 @@ # CHECK-NOT: ${{.*}}print_environment.py # CHECK: Expected Passes : 4 -# CHECK: Unexpected Failures: 11 +# CHECK: Unexpected Failures: 12 # CHECK-NOT: {{.}} Index: llvm/utils/lit/tests/shtest-not.py =================================================================== --- /dev/null +++ llvm/utils/lit/tests/shtest-not.py @@ -0,0 +1,115 @@ +# Check the not command +# +# RUN: not %{lit} -j 1 -a -v %{inputs}/shtest-not \ +# RUN: | FileCheck -match-full-lines %s +# +# END. + +# Make sure not and env commands are included in printed commands. + +# CHECK: -- Testing: 13 tests{{.*}} + +# CHECK: FAIL: shtest-not :: not-args-last-is-crash.txt {{.*}} +# CHECK: $ "not" "--crash" +# CHECK: Error: 'not' requires a subcommand +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-args-nested-none.txt {{.*}} +# CHECK: $ "not" "not" "not" +# CHECK: Error: 'not' requires a subcommand +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-args-none.txt {{.*}} +# CHECK: $ "not" +# CHECK: Error: 'not' requires a subcommand +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-calls-cd.txt {{.*}} +# CHECK: $ "not" "not" "cd" "foobar" +# CHECK: $ "not" "--crash" "cd" "foobar" +# CHECK: Error: 'not --crash' cannot call 'cd' +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-calls-colon.txt {{.*}} +# CHECK: $ "not" "not" ":" "foobar" +# CHECK: $ "not" "--crash" ":" +# CHECK: Error: 'not --crash' cannot call ':' +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-calls-diff-with-crash.txt {{.*}} +# CHECK: $ "not" "--crash" "diff" "-u" {{.*}} +# CHECK-NOT: "$" +# CHECK-NOT: {{[Ee]rror}} +# CHECK: error: command failed with exit status: {{.*}} +# CHECK-NOT: {{[Ee]rror}} +# CHECK-NOT: "$" + +# CHECK: FAIL: shtest-not :: not-calls-diff.txt {{.*}} +# CHECK: $ "not" "diff" {{.*}} +# CHECK: $ "not" "not" "not" "diff" {{.*}} +# CHECK: $ "not" "not" "not" "not" "not" "diff" {{.*}} +# CHECK: $ "diff" {{.*}} +# CHECK: $ "not" "not" "diff" {{.*}} +# CHECK: $ "not" "not" "not" "not" "diff" {{.*}} +# CHECK: $ "not" "diff" {{.*}} +# CHECK-NOT: "$" + +# CHECK: FAIL: shtest-not :: not-calls-echo.txt {{.*}} +# CHECK: $ "not" "not" "echo" "hello" "world" +# CHECK: $ "not" "--crash" "echo" "hello" "world" +# CHECK: Error: 'not --crash' cannot call 'echo' +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-calls-env-builtin.txt {{.*}} +# CHECK: $ "not" "--crash" "env" "-u" "FOO" "BAR=3" "rm" "{{.*}}.no-such-file" +# CHECK: Error: 'env' cannot call 'rm' +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-calls-export.txt {{.*}} +# CHECK: $ "not" "not" "export" "FOO=1" +# CHECK: $ "not" "--crash" "export" "BAZ=3" +# CHECK: Error: 'not --crash' cannot call 'export' +# CHECK: error: command failed with exit status: {{.*}} + + +# CHECK: PASS: shtest-not :: not-calls-external.txt {{.*}} + +# CHECK: $ "not" "{{[^"]*}}" "fail.py" +# CHECK: $ "not" "not" "{{[^"]*}}" "pass.py" +# CHECK: $ "not" "not" "not" "{{[^"]*}}" "fail.py" +# CHECK: $ "not" "not" "not" "not" "{{[^"]*}}" "pass.py" + +# CHECK: $ "not" "not" "--crash" "{{[^"]*}}" "pass.py" +# CHECK: $ "not" "not" "--crash" "{{[^"]*}}" "fail.py" +# CHECK: $ "not" "not" "--crash" "not" "{{[^"]*}}" "pass.py" +# CHECK: $ "not" "not" "--crash" "not" "{{[^"]*}}" "fail.py" + +# CHECK: $ "env" "not" "{{[^"]*}}" "fail.py" +# CHECK: $ "not" "env" "{{[^"]*}}" "fail.py" +# CHECK: $ "env" "FOO=1" "not" "{{[^"]*}}" "fail.py" +# CHECK: $ "not" "env" "FOO=1" "BAR=1" "{{[^"]*}}" "fail.py" +# CHECK: $ "env" "FOO=1" "BAR=1" "not" "env" "-u" "FOO" "BAR=2" "{{[^"]*}}" "fail.py" +# CHECK: $ "not" "env" "FOO=1" "BAR=1" "not" "env" "-u" "FOO" "-u" "BAR" "{{[^"]*}}" "pass.py" +# CHECK: $ "not" "not" "env" "FOO=1" "env" "FOO=2" "BAR=1" "{{[^"]*}}" "pass.py" +# CHECK: $ "env" "FOO=1" "-u" "BAR" "env" "-u" "FOO" "BAR=1" "not" "not" "{{[^"]*}}" "pass.py" + +# CHECK: $ "not" "env" "FOO=1" "BAR=1" "env" "FOO=2" "BAR=2" "not" "--crash" "{{[^"]*}}" "pass.py" +# CHECK: $ "not" "env" "FOO=1" "BAR=1" "not" "--crash" "not" "{{[^"]*}}" "pass.py" +# CHECK: $ "not" "not" "--crash" "env" "-u" "BAR" "not" "env" "-u" "FOO" "BAR=1" "{{[^"]*}}" "pass.py" + + +# CHECK: FAIL: shtest-not :: not-calls-mkdir.txt {{.*}} +# CHECK: $ "not" "mkdir" {{.*}} +# CHECK: $ "not" "--crash" "mkdir" "foobar" +# CHECK: Error: 'not --crash' cannot call 'mkdir' +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: not-calls-rm.txt {{.*}} +# CHECK: $ "not" "rm" {{.*}} +# CHECK: $ "not" "--crash" "rm" "foobar" +# CHECK: Error: 'not --crash' cannot call 'rm' +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: Expected Passes : 1 +# CHECK: Unexpected Failures: 12 +# CHECK-NOT: {{.}}