Index: llvm/utils/lit/lit/TestRunner.py =================================================================== --- llvm/utils/lit/lit/TestRunner.py +++ llvm/utils/lit/lit/TestRunner.py @@ -236,7 +236,9 @@ return ''.join(result) # args are from 'export' or 'env' command. -# Returns copy of args without those commands or their arguments. +# Skips the command, and parses its arguments. +# Modifies env accordingly if env is not None. +# Returns copy of args without the command or its arguments. def updateEnv(env, args): arg_idx = 1 unset_next_env_var = False @@ -249,7 +251,7 @@ continue if unset_next_env_var: unset_next_env_var = False - if arg in env.env: + if env is not None and arg in env.env: del env.env[arg] continue @@ -258,7 +260,8 @@ # Stop if there was no equals. if eq == '': break - env.env[key] = val + if env is not None: + env.env[key] = val return args[arg_idx+1:] def executeBuiltinEcho(cmd, shenv): @@ -795,7 +798,22 @@ assert isinstance(cmd, ShUtil.Pipeline) # Handle shell builtins first. - if cmd.commands[0].args[0] == 'cd': + + # Extract env commands and their arguments so we don't overlook any shell + # builtin. Don't modify the original args as we'll need to process the + # env commands again if this is not a shell builtin. + # + # env calling a builtin is useless, so each shell builtin takes the safe + # approach of complaining about env. + cmd0_has_envs = False + cmd0_args_pruned = list(cmd.commands[0].args) + while len(cmd0_args_pruned) and cmd0_args_pruned[0] == 'env': + cmd0_args_pruned = updateEnv(None, cmd0_args_pruned) + cmd0_has_envs = True + + if cmd0_args_pruned[0] == 'cd': + if cmd0_has_envs: + raise ValueError("'env' cannot call 'cd'") if len(cmd.commands) != 1: raise ValueError("'cd' cannot be part of a pipeline") if len(cmd.commands[0].args) != 2: @@ -815,13 +833,17 @@ # a file. # FIXME: Standardize on the builtin echo implementation. We can use a # temporary file to sidestep blocking pipe write issues. - if cmd.commands[0].args[0] == 'echo' and len(cmd.commands) == 1: + if cmd0_args_pruned[0] == 'echo' and len(cmd.commands) == 1: + if cmd0_has_envs: + raise ValueError("'env' cannot call 'echo'") output = executeBuiltinEcho(cmd.commands[0], shenv) results.append(ShellCommandResult(cmd.commands[0], output, "", 0, False)) return 0 - if cmd.commands[0].args[0] == 'export': + if cmd0_args_pruned[0] == 'export': + if cmd0_has_envs: + raise ValueError("'env' cannot call 'export'") if len(cmd.commands) != 1: raise ValueError("'export' cannot be part of a pipeline") if len(cmd.commands[0].args) != 2: @@ -829,7 +851,9 @@ updateEnv(shenv, cmd.commands[0].args) return 0 - if cmd.commands[0].args[0] == 'mkdir': + if cmd0_args_pruned[0] == 'mkdir': + if cmd0_has_envs: + raise ValueError("'env' cannot call 'mkdir'") if len(cmd.commands) != 1: raise InternalShellError(cmd.commands[0], "Unsupported: 'mkdir' " "cannot be part of a pipeline") @@ -837,7 +861,9 @@ results.append(cmdResult) return cmdResult.exitCode - if cmd.commands[0].args[0] == 'diff': + if cmd0_args_pruned[0] == 'diff': + if cmd0_has_envs: + raise ValueError("'env' cannot call 'diff'") if len(cmd.commands) != 1: raise InternalShellError(cmd.commands[0], "Unsupported: 'diff' " "cannot be part of a pipeline") @@ -845,7 +871,9 @@ results.append(cmdResult) return cmdResult.exitCode - if cmd.commands[0].args[0] == 'rm': + if cmd0_args_pruned[0] == 'rm': + if cmd0_has_envs: + raise ValueError("'env' cannot call 'rm'") if len(cmd.commands) != 1: raise InternalShellError(cmd.commands[0], "Unsupported: 'rm' " "cannot be part of a pipeline") @@ -853,7 +881,9 @@ results.append(cmdResult) return cmdResult.exitCode - if cmd.commands[0].args[0] == ':': + if cmd0_args_pruned[0] == ':': + if cmd0_has_envs: + raise ValueError("'env' cannot call ':'") if len(cmd.commands) != 1: raise InternalShellError(cmd.commands[0], "Unsupported: ':' " "cannot be part of a pipeline") @@ -874,12 +904,16 @@ # Reference the global environment by default. cmd_shenv = shenv args = list(j.args) - if j.args[0] == 'env': + while len(args) and 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: + # 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 - cmd_shenv = ShellEnvironment(shenv.cwd, shenv.env) - args = updateEnv(cmd_shenv, j.args) + # 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) stdin, stdout, stderr = processRedirects(j, default_stdin, cmd_shenv, opened_files) Index: llvm/utils/lit/tests/Inputs/fake-externals/: =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/: @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/fake-externals/cd =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/cd @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/fake-externals/diff =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/diff @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/fake-externals/env =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/env @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/fake-externals/export =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/export @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/fake-externals/fake-external.py =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/fake-external.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import os +import sys + +sys.stderr.write("error: external '{}' command called unexpectedly\n".format( + os.path.basename(__file__))) +sys.exit(1) Index: llvm/utils/lit/tests/Inputs/fake-externals/mkdir =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/mkdir @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/fake-externals/rm =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/fake-externals/rm @@ -0,0 +1 @@ +fake-external.py \ No newline at end of file Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-cd.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-cd.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 cd foobar Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-colon.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-colon.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 : Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-diff.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-diff.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 diff foo.txt bar.txt Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-echo.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-echo.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 echo hello world Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-env.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-env.txt @@ -0,0 +1,32 @@ +# Check that internal env can call internal env. + +# RUN: env env %{python} print_environment.py \ +# RUN: | FileCheck -check-prefix=CHECK-2-EMPTY %s +# +# CHECK-2-EMPTY: BAR = 2 +# CHECK-2-EMPTY: FOO = 1 + +# RUN: env FOO=2 env BAR=1 %{python} print_environment.py \ +# RUN: | FileCheck -check-prefix=CHECK-2-VAL %s +# +# CHECK-2-VAL: BAR = 1 +# CHECK-2-VAL: FOO = 2 + +# RUN: env -u FOO env -u BAR %{python} print_environment.py \ +# RUN: | FileCheck -check-prefix=CHECK-2-U %s +# +# CHECK-2-U-NOT: BAR +# CHECK-2-U-NOT: FOO + +# RUN: env -u FOO BAR=1 env -u BAR FOO=2 %{python} print_environment.py \ +# RUN: | FileCheck -check-prefix=CHECK-2-U-VAL %s +# +# CHECK-2-U-VAL-NOT: BAR +# CHECK-2-U-VAL: FOO = 2 + +# RUN: env -u FOO BAR=1 env -u BAR FOO=2 env BAZ=3 %{python} print_environment.py \ +# RUN: | FileCheck -check-prefix=CHECK-3 %s +# +# CHECK-3-NOT: BAR +# CHECK-3: BAZ = 3 +# CHECK-3: FOO = 2 Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-export.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-export.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 export BAZ=3 Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-mkdir.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-mkdir.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 mkdir foobar Index: llvm/utils/lit/tests/Inputs/shtest-env/env-calls-rm.txt =================================================================== --- /dev/null +++ llvm/utils/lit/tests/Inputs/shtest-env/env-calls-rm.txt @@ -0,0 +1 @@ +# RUN: env -u FOO BAR=3 rm foobar Index: llvm/utils/lit/tests/lit.cfg =================================================================== --- llvm/utils/lit/tests/lit.cfg +++ llvm/utils/lit/tests/lit.cfg @@ -75,3 +75,13 @@ if not llvm_config: if sys.platform.startswith('win') or sys.platform.startswith('cygwin'): config.available_features.add('system-windows') + +# For each of lit's internal shell commands ('env', 'cd', 'diff', etc.), put +# a fake command that always fails at the start of PATH. This helps us check +# that we always use lit's internal version rather than some external version +# that might not be present or behave correctly on all platforms. Don't do +# this for 'echo' because an external version is used when it appears in a +# pipeline. +test_bin = os.path.join(os.path.dirname(__file__), 'Inputs', 'fake-externals') +config.environment['PATH'] = os.path.pathsep.join((test_bin, + config.environment['PATH'])) Index: llvm/utils/lit/tests/shtest-env.py =================================================================== --- llvm/utils/lit/tests/shtest-env.py +++ llvm/utils/lit/tests/shtest-env.py @@ -1,23 +1,59 @@ # Check the env command # -# RUN: %{lit} -j 1 -a -v %{inputs}/shtest-env \ +# RUN: not %{lit} -j 1 -a -v %{inputs}/shtest-env \ # RUN: | FileCheck -match-full-lines %s # # END. # Make sure env commands are included in printed commands. +# CHECK: -- Testing: 11 tests{{.*}} + +# CHECK: UNRESOLVED: shtest-env :: env-calls-cd.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call 'cd' + +# CHECK: UNRESOLVED: shtest-env :: env-calls-colon.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call ':' + +# CHECK: UNRESOLVED: shtest-env :: env-calls-diff.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call 'diff' + +# CHECK: UNRESOLVED: shtest-env :: env-calls-echo.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call 'echo' + +# CHECK: PASS: shtest-env :: env-calls-env.txt ({{[^)]*}}) +# CHECK: $ "env" "env" "{{[^"]*}}" "print_environment.py" +# CHECK: $ "env" "FOO=2" "env" "BAR=1" "{{[^"]*}}" "print_environment.py" +# CHECK: $ "env" "-u" "FOO" "env" "-u" "BAR" "{{[^"]*}}" "print_environment.py" +# CHECK: $ "env" "-u" "FOO" "BAR=1" "env" "-u" "BAR" "FOO=2" "{{[^"]*}}" "print_environment.py" +# CHECK: $ "env" "-u" "FOO" "BAR=1" "env" "-u" "BAR" "FOO=2" "env" "BAZ=3" "{{[^"]*}}" "print_environment.py" +# CHECK-NOT: ${{.*}}print_environment.py + +# CHECK: UNRESOLVED: shtest-env :: env-calls-export.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call 'export' + +# CHECK: UNRESOLVED: shtest-env :: env-calls-mkdir.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call 'mkdir' + +# CHECK: UNRESOLVED: shtest-env :: env-calls-rm.txt ({{[^)]*}}) +# CHECK: ValueError: 'env' cannot call 'rm' + # CHECK: PASS: shtest-env :: env-u.txt ({{[^)]*}}) # CHECK: $ "{{[^"]*}}" "print_environment.py" # CHECK: $ "env" "-u" "FOO" "{{[^"]*}}" "print_environment.py" # CHECK: $ "env" "-u" "FOO" "-u" "BAR" "{{[^"]*}}" "print_environment.py" +# CHECK-NOT: ${{.*}}print_environment.py # CHECK: PASS: shtest-env :: env.txt ({{[^)]*}}) # CHECK: $ "env" "A_FOO=999" "{{[^"]*}}" "print_environment.py" # CHECK: $ "env" "A_FOO=1" "B_BAR=2" "C_OOF=3" "{{[^"]*}}" "print_environment.py" +# CHECK-NOT: ${{.*}}print_environment.py # CHECK: PASS: shtest-env :: mixed.txt ({{[^)]*}}) # CHECK: $ "env" "A_FOO=999" "-u" "FOO" "{{[^"]*}}" "print_environment.py" # CHECK: $ "env" "A_FOO=1" "-u" "FOO" "B_BAR=2" "-u" "BAR" "C_OOF=3" "{{[^"]*}}" "print_environment.py" +# CHECK-NOT: ${{.*}}print_environment.py -# CHECK: Expected Passes : 3 +# CHECK: Expected Passes : 4 +# CHECK: Unresolved Tests : 7 +# CHECK-NOT: {{.}}