diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py --- a/llvm/utils/lit/lit/TestRunner.py +++ b/llvm/utils/lit/lit/TestRunner.py @@ -614,6 +614,7 @@ assert isinstance(cmd, ShUtil.Pipeline) procs = [] + negate_procs = [] default_stdin = subprocess.PIPE stderrTempFiles = [] opened_files = [] @@ -659,6 +660,12 @@ if not args: raise InternalShellError(j, "Error: 'not' requires a" " subcommand") + elif args[0] == '!': + not_args.append(args.pop(0)) + not_count += 1 + if not args: + raise InternalShellError(j, "Error: '!' requires a" + " subcommand") else: break @@ -705,7 +712,15 @@ # 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 + + # For plain negations, either 'not' without '--crash', or the shell + # operator '!', leave them out from the command to execute and + # invert the result code afterwards. + if not_crash: + args = not_args + args + not_count = 0 + else: + not_args = [] stdin, stdout, stderr = processRedirects(j, default_stdin, cmd_shenv, opened_files) @@ -769,6 +784,7 @@ stderr = stderr, env = cmd_shenv.env, close_fds = kUseCloseFDs)) + negate_procs.append((not_count % 2) != 0) # Let the helper know about this process timeoutHelper.addProcess(procs[-1]) except OSError as e: @@ -821,6 +837,8 @@ # Detect Ctrl-C in subprocess. if res == -signal.SIGINT: raise KeyboardInterrupt + if negate_procs[i]: + res = not res # Ensure the resulting output is always of string type. try: diff --git a/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-args-nested-none.txt b/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-args-nested-none.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-args-nested-none.txt @@ -0,0 +1 @@ +# RUN: ! ! ! diff --git a/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-args-none.txt b/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-args-none.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-args-none.txt @@ -0,0 +1 @@ +# RUN: ! diff --git a/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-calls-external.txt b/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-calls-external.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/shtest-not/exclamation-calls-external.txt @@ -0,0 +1,9 @@ +# Simple uses of '!' + +# RUN: ! %{python} fail.py +# RUN: ! ! %{python} pass.py +# RUN: ! ! ! %{python} fail.py +# RUN: ! ! ! ! %{python} pass.py + +# pass.py succeeds but we expect failure +# RUN: ! %{python} pass.py diff --git a/llvm/utils/lit/tests/lit.cfg b/llvm/utils/lit/tests/lit.cfg --- a/llvm/utils/lit/tests/lit.cfg +++ b/llvm/utils/lit/tests/lit.cfg @@ -95,7 +95,8 @@ # 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. Don't do this for ':' because it doesn't appear to be a valid file -# name under Windows. +# name under Windows. Don't do this for 'not' because lit uses the external +# 'not' throughout a RUN line that calls 'not --crash'. test_bin = os.path.join(os.path.dirname(__file__), 'Inputs', 'fake-externals') config.environment['PATH'] = os.path.pathsep.join((test_bin, config.environment['PATH'])) diff --git a/llvm/utils/lit/tests/shtest-not.py b/llvm/utils/lit/tests/shtest-not.py --- a/llvm/utils/lit/tests/shtest-not.py +++ b/llvm/utils/lit/tests/shtest-not.py @@ -10,7 +10,27 @@ # Make sure not and env commands are included in printed commands. -# CHECK: -- Testing: 13 tests{{.*}} +# CHECK: -- Testing: 16 tests{{.*}} + +# CHECK: FAIL: shtest-not :: exclamation-args-nested-none.txt {{.*}} +# CHECK: $ "!" "!" "!" +# CHECK: Error: '!' requires a subcommand +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: exclamation-args-none.txt {{.*}} +# CHECK: $ "!" +# CHECK: Error: '!' requires a subcommand +# CHECK: error: command failed with exit status: {{.*}} + +# CHECK: FAIL: shtest-not :: exclamation-calls-external.txt {{.*}} + +# CHECK: $ "!" "{{[^"]*}}" "fail.py" +# CHECK: $ "!" "!" "{{[^"]*}}" "pass.py" +# CHECK: $ "!" "!" "!" "{{[^"]*}}" "fail.py" +# CHECK: $ "!" "!" "!" "!" "{{[^"]*}}" "pass.py" + +# CHECK: $ "!" "{{[^"]*}}" "pass.py" +# CHECK: error: command failed with exit status: {{.*}} # CHECK: FAIL: shtest-not :: not-args-last-is-crash.txt {{.*}} # CHECK: $ "not" "--crash" @@ -114,5 +134,5 @@ # CHECK: error: command failed with exit status: {{.*}} # CHECK: Passed: 1 -# CHECK: Failed: 12 +# CHECK: Failed: 15 # CHECK-NOT: {{.}}