Skip to content

Conversation

hnrklssn
Copy link
Member

The early exit we relied on to only invoke test updaters for failing tests requires that there was no output to stdout or stderr, and that timeouts weren't enabled. When these conditions weren't fulfilled, test updaters would be invoked even on passing or XFAILed tests.

The early exit we relied on to only invoke test updaters for failing
tests requires that there was no output to stdout or stderr, and that
timeouts weren't enabled. When these conditions weren't fulfilled, test
updaters would be invoked even on passing or XFAILed tests.
@llvmbot
Copy link
Member

llvmbot commented Aug 24, 2025

@llvm/pr-subscribers-testing-tools

Author: Henrik G. Olsson (hnrklssn)

Changes

The early exit we relied on to only invoke test updaters for failing tests requires that there was no output to stdout or stderr, and that timeouts weren't enabled. When these conditions weren't fulfilled, test updaters would be invoked even on passing or XFAILed tests.


Full diff: https://github.com/llvm/llvm-project/pull/155148.diff

10 Files Affected:

  • (modified) llvm/utils/lit/lit/TestRunner.py (+23-3)
  • (modified) llvm/utils/lit/lit/worker.py (+5)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/fail.test (+1)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/lit.cfg (+14)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/pass-silent.test (+2)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/pass.test (+1)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/should_not_run.py (+3)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/xfail.test (+2)
  • (added) llvm/utils/lit/tests/Inputs/pass-test-update/xpass.test (+2)
  • (added) llvm/utils/lit/tests/pass-test-update.py (+40)
diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index f2c5c6d0dbe93..516c63ec9e78e 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -13,6 +13,7 @@
 import tempfile
 import threading
 import typing
+import traceback
 from typing import Optional, Tuple
 
 import io
@@ -47,6 +48,16 @@ def __init__(self, message):
         super().__init__(message)
 
 
+class TestUpdaterException(Exception):
+    """
+    There was an error not during test execution, but while invoking a function
+    in test_updaters on a failing RUN line.
+    """
+
+    def __init__(self, message):
+        super().__init__(message)
+
+
 kIsWindows = platform.system() == "Windows"
 
 # Don't use close_fds on Windows.
@@ -1192,13 +1203,22 @@ def executeScriptInternal(
                 str(result.timeoutReached),
             )
 
-        if litConfig.update_tests:
+        if (litConfig.update_tests
+            and result.exitCode != 0
+            and not timeoutInfo
+            # Don't run test updaters on XPASS failures -
+            # they are not expected to be able to fix them
+            and not test.isExpectedToFail()
+        ):
             for test_updater in litConfig.test_updaters:
                 try:
                     update_output = test_updater(result, test)
                 except Exception as e:
-                    out += f"Exception occurred in test updater: {e}"
-                    continue
+                    output = out
+                    output += err
+                    output += "Exception occurred in test updater:\n"
+                    output += traceback.format_exc()
+                    raise TestUpdaterException(output)
                 if update_output:
                     for line in update_output.splitlines():
                         out += f"# {line}\n"
diff --git a/llvm/utils/lit/lit/worker.py b/llvm/utils/lit/lit/worker.py
index 8e78bfd45d38b..0e36f3e9a2424 100644
--- a/llvm/utils/lit/lit/worker.py
+++ b/llvm/utils/lit/lit/worker.py
@@ -13,6 +13,7 @@
 
 import lit.Test
 import lit.util
+from lit.TestRunner import TestUpdaterException
 
 
 _lit_config = None
@@ -75,6 +76,10 @@ def _execute_test_handle_errors(test, lit_config):
     try:
         result = test.config.test_format.execute(test, lit_config)
         return _adapt_result(result)
+    except TestUpdaterException as e:
+        if lit_config.debug:
+            raise
+        return lit.Test.Result(lit.Test.UNRESOLVED, str(e))
     except:
         if lit_config.debug:
             raise
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/fail.test b/llvm/utils/lit/tests/Inputs/pass-test-update/fail.test
new file mode 100644
index 0000000000000..a410ef9293129
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/fail.test
@@ -0,0 +1 @@
+# RUN: not echo "fail"
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/lit.cfg b/llvm/utils/lit/tests/Inputs/pass-test-update/lit.cfg
new file mode 100644
index 0000000000000..e0dbc3220678c
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/lit.cfg
@@ -0,0 +1,14 @@
+import lit.formats
+
+config.name = "pass-test-update"
+config.suffixes = [".test"]
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+import sys
+import os
+sys.path.append(os.path.dirname(__file__))
+from should_not_run import should_not_run
+lit_config.test_updaters.append(should_not_run)
+
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/pass-silent.test b/llvm/utils/lit/tests/Inputs/pass-test-update/pass-silent.test
new file mode 100644
index 0000000000000..07cafbc66a897
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/pass-silent.test
@@ -0,0 +1,2 @@
+# RUN: echo ""
+
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/pass.test b/llvm/utils/lit/tests/Inputs/pass-test-update/pass.test
new file mode 100644
index 0000000000000..3a12c5fee4bb0
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/pass.test
@@ -0,0 +1 @@
+# RUN: echo "passed"
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/should_not_run.py b/llvm/utils/lit/tests/Inputs/pass-test-update/should_not_run.py
new file mode 100644
index 0000000000000..adba049753594
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/should_not_run.py
@@ -0,0 +1,3 @@
+def should_not_run(foo, bar):
+    raise Exception("this test updater should only run on failure")
+
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/xfail.test b/llvm/utils/lit/tests/Inputs/pass-test-update/xfail.test
new file mode 100644
index 0000000000000..044b6a19b4a19
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/xfail.test
@@ -0,0 +1,2 @@
+# XFAIL: *
+# RUN: not echo "failed"
diff --git a/llvm/utils/lit/tests/Inputs/pass-test-update/xpass.test b/llvm/utils/lit/tests/Inputs/pass-test-update/xpass.test
new file mode 100644
index 0000000000000..e0c1c39762ee9
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/pass-test-update/xpass.test
@@ -0,0 +1,2 @@
+# XFAIL: *
+# RUN: echo "accidentally passed"
diff --git a/llvm/utils/lit/tests/pass-test-update.py b/llvm/utils/lit/tests/pass-test-update.py
new file mode 100644
index 0000000000000..b3cb7057ef163
--- /dev/null
+++ b/llvm/utils/lit/tests/pass-test-update.py
@@ -0,0 +1,40 @@
+# RUN: %{lit} --update-tests --ignore-fail -v %S/Inputs/pass-test-update | FileCheck %s --implicit-check-not Exception
+
+# CHECK:      UNRESOLVED: pass-test-update :: fail.test (1 of 5)
+# CHECK-NEXT: ******************** TEST 'pass-test-update :: fail.test' FAILED ********************
+# CHECK-NEXT: # {{R}}UN: at line 1
+# CHECK-NEXT: not echo "fail"
+# CHECK-NEXT: # executed command: not echo fail
+# CHECK-NEXT: # .---command stdout------------
+# CHECK-NEXT: # | fail
+# CHECK-NEXT: # `-----------------------------
+# CHECK-NEXT: # error: command failed with exit status: 1
+# CHECK-NEXT: Exception occurred in test updater:
+# CHECK-NEXT: Traceback (most recent call last):
+# CHECK-NEXT:   File {{.*}}, line {{.*}}, in {{.*}}
+# CHECK-NEXT:     update_output = test_updater(result, test)
+# CHECK-NEXT:   File "{{.*}}/should_not_run.py", line {{.*}}, in should_not_run
+# CHECK-NEXT:     raise Exception("this test updater should only run on failure")
+# CHECK-NEXT: Exception: this test updater should only run on failure
+# CHECK-EMPTY:
+# CHECK-NEXT: ********************
+# CHECK-NEXT: PASS: pass-test-update :: pass-silent.test (2 of 5)
+# CHECK-NEXT: PASS: pass-test-update :: pass.test (3 of 5)
+# CHECK-NEXT: {{X}}FAIL: pass-test-update :: xfail.test (4 of 5)
+# CHECK-NEXT: XPASS: pass-test-update :: xpass.test (5 of 5)
+# CHECK-NEXT: ******************** TEST 'pass-test-update :: xpass.test' FAILED ********************
+# CHECK-NEXT: Exit Code: 0
+# CHECK-EMPTY:
+# CHECK-NEXT: Command Output (stdout):
+# CHECK-NEXT: --
+# CHECK-NEXT: # {{R}}UN: at line 2
+# CHECK-NEXT: echo "accidentally passed"
+# CHECK-NEXT: # executed command: echo 'accidentally passed'
+# CHECK-NEXT: # .---command stdout------------
+# CHECK-NEXT: # | accidentally passed
+# CHECK-NEXT: # `-----------------------------
+# CHECK-EMPTY:
+# CHECK-NEXT: --
+# CHECK-EMPTY:
+# CHECK-NEXT: ********************
+

Copy link

github-actions bot commented Aug 24, 2025

✅ With the latest revision this PR passed the Python code formatter.

@hnrklssn hnrklssn merged commit 58996c0 into llvm:main Aug 25, 2025
8 of 10 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Aug 25, 2025

LLVM Buildbot has detected a new failure on builder ml-opt-devrel-x86-64 running on ml-opt-devrel-x86-64-b1 while building llvm at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/175/builds/24070

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'lit :: pass-test-update.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
env -u FILECHECK_OPTS "/usr/bin/python3" /b/ml-opt-devrel-x86-64-b1/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical --update-tests --ignore-fail -v /b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update | FileCheck /b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/pass-test-update.py --implicit-check-not Exception
# executed command: env -u FILECHECK_OPTS /usr/bin/python3 /b/ml-opt-devrel-x86-64-b1/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical --update-tests --ignore-fail -v /b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update
# .---command stderr------------
# | 
# | Exiting with status 0 instead of 1 because '--ignore-fail' was specified.
# `-----------------------------
# executed command: FileCheck /b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/pass-test-update.py --implicit-check-not Exception
# .---command stderr------------
# | /b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/pass-test-update.py:16:15: error: CHECK-NEXT: is not on the line after the previous match
# | # CHECK-NEXT: File "{{.*}}/should_not_run.py", line {{.*}}, in should_not_run
# |               ^
# | <stdin>:16:2: note: 'next' match was here
# |  File "/b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update/should_not_run.py", line 2, in should_not_run
# |  ^
# | <stdin>:14:44: note: previous match ended here
# |  update_output = test_updater(result, test)
# |                                            ^
# | <stdin>:15:1: note: non-matching line after previous match is here
# |  ^^^^^^^^^^^^^^^^^^^^^^^^^^
# | ^
# | 
# | Input file: <stdin>
# | Check file: /b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/pass-test-update.py
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |         11: Exception occurred in test updater: 
# |         12: Traceback (most recent call last): 
# |         13:  File "/b/ml-opt-devrel-x86-64-b1/llvm-project/llvm/utils/lit/lit/TestRunner.py", line 1216, in executeScriptInternal 
# |         14:  update_output = test_updater(result, test) 
# |         15:  ^^^^^^^^^^^^^^^^^^^^^^^^^^ 
# |         16:  File "/b/ml-opt-devrel-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update/should_not_run.py", line 2, in should_not_run 
# | next:16      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  error: match on wrong line
# |         17:  raise Exception("this test updater should only run on failure") 
# |         18: Exception: this test updater should only run on failure 
# |         19:  
# |         20: ******************** 
# |         21: PASS: pass-test-update :: pass-silent.test (2 of 5) 
# |          .
...

@llvm-ci
Copy link
Collaborator

llvm-ci commented Aug 25, 2025

LLVM Buildbot has detected a new failure on builder ml-opt-dev-x86-64 running on ml-opt-dev-x86-64-b2 while building llvm at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/137/builds/24222

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'lit :: pass-test-update.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
env -u FILECHECK_OPTS "/usr/bin/python3" /b/ml-opt-dev-x86-64-b1/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical --update-tests --ignore-fail -v /b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update | FileCheck /b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/pass-test-update.py --implicit-check-not Exception
# executed command: env -u FILECHECK_OPTS /usr/bin/python3 /b/ml-opt-dev-x86-64-b1/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical --update-tests --ignore-fail -v /b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update
# .---command stderr------------
# | 
# | Exiting with status 0 instead of 1 because '--ignore-fail' was specified.
# `-----------------------------
# executed command: FileCheck /b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/pass-test-update.py --implicit-check-not Exception
# .---command stderr------------
# | /b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/pass-test-update.py:16:15: error: CHECK-NEXT: is not on the line after the previous match
# | # CHECK-NEXT: File "{{.*}}/should_not_run.py", line {{.*}}, in should_not_run
# |               ^
# | <stdin>:16:2: note: 'next' match was here
# |  File "/b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update/should_not_run.py", line 2, in should_not_run
# |  ^
# | <stdin>:14:44: note: previous match ended here
# |  update_output = test_updater(result, test)
# |                                            ^
# | <stdin>:15:1: note: non-matching line after previous match is here
# |  ^^^^^^^^^^^^^^^^^^^^^^^^^^
# | ^
# | 
# | Input file: <stdin>
# | Check file: /b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/pass-test-update.py
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |         11: Exception occurred in test updater: 
# |         12: Traceback (most recent call last): 
# |         13:  File "/b/ml-opt-dev-x86-64-b1/llvm-project/llvm/utils/lit/lit/TestRunner.py", line 1216, in executeScriptInternal 
# |         14:  update_output = test_updater(result, test) 
# |         15:  ^^^^^^^^^^^^^^^^^^^^^^^^^^ 
# |         16:  File "/b/ml-opt-dev-x86-64-b1/build/utils/lit/tests/Inputs/pass-test-update/should_not_run.py", line 2, in should_not_run 
# | next:16      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  error: match on wrong line
# |         17:  raise Exception("this test updater should only run on failure") 
# |         18: Exception: this test updater should only run on failure 
# |         19:  
# |         20: ******************** 
# |         21: PASS: pass-test-update :: pass-silent.test (2 of 5) 
# |          .
...

@hnrklssn
Copy link
Member Author

follow-up to address buildbot failures: #155303

dyung added a commit to dyung/llvm-project that referenced this pull request Aug 26, 2025
dyung added a commit that referenced this pull request Aug 26, 2025
…155354)

Should fix Windows build bot failures such as
https://lab.llvm.org/buildbot/#/builders/46/builds/22281.

The test (and the followup fix in #155303) did not properly account for
Windows style path separators.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants