[llvm] 809b4cf - [llvm-lit] Error on malformed `.lit_test_times` entries instead of stack trace (#191305)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 17 11:49:27 PDT 2026


Author: Jonathan Thackray
Date: 2026-04-17T19:49:22+01:00
New Revision: 809b4cfa2b15f1e5699ff00c842266e5362ccd32

URL: https://github.com/llvm/llvm-project/commit/809b4cfa2b15f1e5699ff00c842266e5362ccd32
DIFF: https://github.com/llvm/llvm-project/commit/809b4cfa2b15f1e5699ff00c842266e5362ccd32.diff

LOG: [llvm-lit] Error on malformed `.lit_test_times` entries instead of stack trace (#191305)

When running `llvm-lit`, I sometimes hit a traceback, because a
`.lit_test_times.txt` file has got corrupted (not sure how).
However, it's non-obvious what the issue is (you just get a traceback),
so I've fixed this as follows:

`read_test_times()` currently assumes every line in
`.lit_test_times.txt`
contains a floating-point time followed by a test path. If the file
contains a blank line, a line without a path, or a non-numeric time,
`llvm-lit` will now error with: `fatal: found malformed timing data in`
+ filename.

Add test coverage for this too.

Added: 
    llvm/utils/lit/tests/Inputs/malformed-test-times/a.txt
    llvm/utils/lit/tests/Inputs/malformed-test-times/b.txt
    llvm/utils/lit/tests/Inputs/malformed-test-times/lit.cfg
    llvm/utils/lit/tests/Inputs/malformed-test-times/lit_test_times
    llvm/utils/lit/tests/malformed-test-times.py

Modified: 
    llvm/utils/lit/lit/Test.py
    llvm/utils/lit/lit/TestTimes.py
    llvm/utils/lit/lit/discovery.py

Removed: 
    


################################################################################
diff  --git a/llvm/utils/lit/lit/Test.py b/llvm/utils/lit/lit/Test.py
index 23fc61756afcf..daba14a898c4c 100644
--- a/llvm/utils/lit/lit/Test.py
+++ b/llvm/utils/lit/lit/Test.py
@@ -222,14 +222,14 @@ class TestSuite:
     A test suite groups together a set of logically related tests.
     """
 
-    def __init__(self, name, source_root, exec_root, config):
+    def __init__(self, name, source_root, exec_root, config, lit_config=None):
         self.name = name
         self.source_root = source_root
         self.exec_root = exec_root
         # The test suite configuration.
         self.config = config
 
-        self.test_times = read_test_times(self)
+        self.test_times = read_test_times(self, lit_config)
 
     def getSourcePath(self, components):
         return os.path.join(self.source_root, *components)

diff  --git a/llvm/utils/lit/lit/TestTimes.py b/llvm/utils/lit/lit/TestTimes.py
index 7ec1a1b97b717..02820016e1d96 100644
--- a/llvm/utils/lit/lit/TestTimes.py
+++ b/llvm/utils/lit/lit/TestTimes.py
@@ -1,16 +1,35 @@
 import os
 
 
-def read_test_times(suite):
-    test_times = {}
+def _get_test_times_path(suite):
     test_times_file = os.path.join(suite.exec_root, ".lit_test_times.txt")
     if not os.path.exists(test_times_file):
         test_times_file = os.path.join(suite.source_root, ".lit_test_times.txt")
+    return test_times_file
+
+
+def read_test_times(suite, lit_config=None):
+    test_times = {}
+    test_times_file = _get_test_times_path(suite)
     if os.path.exists(test_times_file):
+        malformed_data = False
         with open(test_times_file, "r") as time_file:
             for line in time_file:
-                time, path = line.split(maxsplit=1)
-                test_times[path.strip("\n")] = float(time)
+                fields = line.split(maxsplit=1)
+                if len(fields) != 2:
+                    malformed_data = True
+                    continue
+                time, path = fields
+                try:
+                    test_times[path.strip("\n")] = float(time)
+                except ValueError:
+                    malformed_data = True
+                    continue
+        if malformed_data and lit_config:
+            lit_config.fatal(
+                "found malformed timing data in %r; remove the file to "
+                "regenerate it" % (test_times_file,)
+            )
     return test_times
 
 
@@ -21,7 +40,7 @@ def record_test_times(tests, lit_config):
         if t.result.elapsed is None:
             continue
         if not t.suite.exec_root in times_by_suite:
-            times_by_suite[t.suite.exec_root] = read_test_times(t.suite)
+            times_by_suite[t.suite.exec_root] = read_test_times(t.suite, lit_config)
 
         # Mark the elapsed time for failed tests as negative so LIT can distingiush failed from
         # successful test runs just based on the time value. For this heuristic to work for tests

diff  --git a/llvm/utils/lit/lit/discovery.py b/llvm/utils/lit/lit/discovery.py
index ac06223b45345..bede6300fb031 100644
--- a/llvm/utils/lit/lit/discovery.py
+++ b/llvm/utils/lit/lit/discovery.py
@@ -68,7 +68,7 @@ def search1(path):
         cfg.load_from_path(cfgpath, litConfig)
         source_root = util.abs_path_preserve_drive(cfg.test_source_root or path)
         exec_root = util.abs_path_preserve_drive(cfg.test_exec_root or path)
-        return Test.TestSuite(cfg.name, source_root, exec_root, cfg), ()
+        return Test.TestSuite(cfg.name, source_root, exec_root, cfg, litConfig), ()
 
     def search(path):
         # Check for an already instantiated test suite.

diff  --git a/llvm/utils/lit/tests/Inputs/malformed-test-times/a.txt b/llvm/utils/lit/tests/Inputs/malformed-test-times/a.txt
new file mode 100644
index 0000000000000..b80b60b7a2794
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/malformed-test-times/a.txt
@@ -0,0 +1 @@
+# RUN: true

diff  --git a/llvm/utils/lit/tests/Inputs/malformed-test-times/b.txt b/llvm/utils/lit/tests/Inputs/malformed-test-times/b.txt
new file mode 100644
index 0000000000000..b80b60b7a2794
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/malformed-test-times/b.txt
@@ -0,0 +1 @@
+# RUN: true

diff  --git a/llvm/utils/lit/tests/Inputs/malformed-test-times/lit.cfg b/llvm/utils/lit/tests/Inputs/malformed-test-times/lit.cfg
new file mode 100644
index 0000000000000..0558595a8c305
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/malformed-test-times/lit.cfg
@@ -0,0 +1,7 @@
+import lit.formats
+
+config.name = "malformed-test-times"
+config.suffixes = [".txt"]
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None

diff  --git a/llvm/utils/lit/tests/Inputs/malformed-test-times/lit_test_times b/llvm/utils/lit/tests/Inputs/malformed-test-times/lit_test_times
new file mode 100644
index 0000000000000..28c0b7da7aba0
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/malformed-test-times/lit_test_times
@@ -0,0 +1,5 @@
+4.798174e-03 b.txt
+missing-path
+
+not-a-float ignored.txt
+1.412153e-03 a.txt

diff  --git a/llvm/utils/lit/tests/malformed-test-times.py b/llvm/utils/lit/tests/malformed-test-times.py
new file mode 100644
index 0000000000000..95e98139404c2
--- /dev/null
+++ b/llvm/utils/lit/tests/malformed-test-times.py
@@ -0,0 +1,12 @@
+## Check that malformed .lit_test_times.txt aborts discovery with a
+## clear diagnostic.
+
+# RUN: cp %{inputs}/malformed-test-times/lit_test_times %{inputs}/malformed-test-times/.lit_test_times.txt
+# RUN: not %{lit-no-order-opt} %{inputs}/malformed-test-times > %t.out 2> %t.err
+# RUN: FileCheck --allow-empty --check-prefix=OUT < %t.out %s
+# RUN: FileCheck --check-prefix=ERR < %t.err %s
+
+# OUT-NOT: -- Testing:
+
+# ERR: fatal: found malformed timing data in
+# ERR-SAME: ; remove the file to regenerate it


        


More information about the llvm-commits mailing list