[llvm] [lit] Fix some issues from --per-test-coverage (PR #65242)

Joel E. Denny via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 11 09:08:20 PDT 2023


https://github.com/jdenny-ornl updated https://github.com/llvm/llvm-project/pull/65242:

>From 21d80c6815a022bc439b82bc995ac9078ad070c8 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Sat, 9 Sep 2023 21:29:56 -0400
Subject: [PATCH 1/2] [lit] Fix some issues from --per-test-coverage

D154280 (landed in 64d19542e78a in July, 2023) implements
`--per-test-coverage` (which can also be specified via
`lit_config.per_test_coverage`).  However, it has a few issues, which
the current patch addresses:

1. D154280 implements `--per-test-coverage` only for the case that lit
   is configured to use an external shell.  The current patch extends
   the implementation to lit's internal shell.

2. In the case that lit is configured to use an external shell,
   regardless of whether `--per-test-coverage` is actually specified,
   D154280 causes `%dbg(RUN: at line N)` to be expanded in RUN lines
   early and in a manner that is specific to sh-like shells.  As a
   result, later code in lit that expands it in a shell-specific
   manner is useless as there's nothing left to expand.  The current
   patch cleans up the implementation to avoid useless code.

3. Because of issue 2, D154280 corrupts support for windows `cmd` as
   an external shell (effectively comments out all RUN lines with
   `:`).  The current patch happens to fix that particular corruption
   by addressing issue 2.  However, D122569 (landed in 1041a9642ba0 in
   April, 2022) had already broken support for windows `cmd` as an
   external shell (discards RUN lines when expanding `%dbg(RUN: at
   line N)`).  The current patch does not attempt to fix that bug.
   For further details, see the PR discussion of the current patch.

The current patch addresses the above issues by implementing
`--per-test-coverage` before selecting the shell (internal or
external) and by leaving `%dbg(RUN: at line N)` unexpanded there.
Thus, it is expanded later in a shell-specific manner, as before
D154280.

The original `--per-test-coverage` implementation avoided accumulating
`export LLVM_PROFILE_FILE={profile}` insertions across retries (due to
`ALLOW_RETRIES`) by skipping the insertion if `%dbg(RUN: at line N)`
was not present and thus had already been expanded.  However, the
current patch makes sure the insertions also happen for commands
without `%dbg(RUN: at line N)`, such as preamble commands or some
commands from other lit test formats.  Thus, the current patch
implements a different mechanism to avoid accumulating those
insertions (see code comments).
---
 llvm/utils/lit/lit/TestRunner.py              | 59 ++++++++++++-------
 .../per-test-coverage-by-lit-cfg/lit.cfg      |  5 +-
 .../per-test-coverage-by-lit-cfg.py           |  5 +-
 .../tests/Inputs/per-test-coverage/lit.cfg    |  6 +-
 .../per-test-coverage/per-test-coverage.py    |  5 +-
 llvm/utils/lit/tests/allow-retries.py         | 12 ++++
 .../lit/tests/per-test-coverage-by-lit-cfg.py | 26 ++++++--
 llvm/utils/lit/tests/per-test-coverage.py     | 27 +++++++--
 8 files changed, 108 insertions(+), 37 deletions(-)

diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 88755297c8e791a..e9e051086f3531a 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -57,6 +57,14 @@ def __init__(self, command, message):
 kPdbgRegex = "%dbg\\(([^)'\"]*)\\)(.*)"
 
 
+def buildPdbgCommand(msg, cmd):
+    res = f"%dbg({msg}) {cmd}"
+    assert re.match(
+        kPdbgRegex, res
+    ), f"kPdbgRegex expected to match actual %dbg usage: {res}"
+    return res
+
+
 class ShellEnvironment(object):
 
     """Mutable shell environment containing things like CWD and env vars.
@@ -1067,25 +1075,10 @@ def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
 def executeScript(test, litConfig, tmpBase, commands, cwd):
     bashPath = litConfig.getBashPath()
     isWin32CMDEXE = litConfig.isWindows and not bashPath
-    coverage_index = 0  # Counter for coverage file index
     script = tmpBase + ".script"
     if isWin32CMDEXE:
         script += ".bat"
 
-    # Set unique LLVM_PROFILE_FILE for each run command
-    for j, ln in enumerate(commands):
-        match = re.match(kPdbgRegex, ln)
-        if match:
-            command = match.group(2)
-            commands[j] = match.expand(": '\\1'; \\2" if command else ": '\\1'")
-            if litConfig.per_test_coverage:
-                # Extract the test case name from the test object
-                test_case_name = test.path_in_suite[-1]
-                test_case_name = test_case_name.rsplit(".", 1)[0]  # Remove the file extension
-                llvm_profile_file = f"{test_case_name}{coverage_index}.profraw"
-                commands[j] = f"export LLVM_PROFILE_FILE={llvm_profile_file} && {commands[j]}"
-                coverage_index += 1
-
     # Write script file
     mode = "w"
     open_kwargs = {}
@@ -1837,13 +1830,7 @@ def _handleCommand(cls, line_number, line, output, keyword):
         if not output or not output[-1].add_continuation(line_number, keyword, line):
             if output is None:
                 output = []
-            pdbg = "%dbg({keyword} at line {line_number})".format(
-                keyword=keyword, line_number=line_number
-            )
-            assert re.match(
-                kPdbgRegex + "$", pdbg
-            ), "kPdbgRegex expected to match actual %dbg usage"
-            line = "{pdbg} {real_command}".format(pdbg=pdbg, real_command=line)
+            line = buildPdbgCommand(f"{keyword} at line {line_number}", line)
             output.append(CommandDirective(line_number, line_number, keyword, line))
         return output
 
@@ -2048,6 +2035,27 @@ def parseIntegratedTestScript(test, additional_parsers=[], require_script=True):
 
 def _runShTest(test, litConfig, useExternalSh, script, tmpBase):
     def runOnce(execdir):
+        # Set unique LLVM_PROFILE_FILE for each run command
+        if litConfig.per_test_coverage:
+            # Extract the test case name from the test object, and remove the
+            # file extension.
+            test_case_name = test.path_in_suite[-1]
+            test_case_name = test_case_name.rsplit(".", 1)[0]
+            coverage_index = 0  # Counter for coverage file index
+            for i, ln in enumerate(script):
+                match = re.match(kPdbgRegex, ln)
+                if match:
+                    dbg = match.group(1)
+                    command = match.group(2)
+                else:
+                    command = ln
+                profile = f"{test_case_name}{coverage_index}.profraw"
+                coverage_index += 1
+                command = f"export LLVM_PROFILE_FILE={profile}; {command}"
+                if match:
+                    command = buildPdbgCommand(dbg, command)
+                script[i] = command
+
         if useExternalSh:
             res = executeScript(test, litConfig, tmpBase, script, execdir)
         else:
@@ -2071,7 +2079,14 @@ def runOnce(execdir):
     # Re-run failed tests up to test.allowed_retries times.
     execdir = os.path.dirname(test.getExecPath())
     attempts = test.allowed_retries + 1
+    scriptInit = script
     for i in range(attempts):
+        # runOnce modifies script, but applying the modifications again to the
+        # result can corrupt script, so we restore the original upon a retry.
+        # A cleaner solution would be for runOnce to encapsulate operating on a
+        # copy of script, but we actually want it to modify the original script
+        # so we can print the modified version under "Script:" below.
+        script = scriptInit[:]
         res = runOnce(execdir)
         if isinstance(res, lit.Test.Result):
             return res
diff --git a/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/lit.cfg b/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/lit.cfg
index 186c81ebd172831..b83d61eec595b5d 100644
--- a/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/lit.cfg
+++ b/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/lit.cfg
@@ -3,6 +3,9 @@ import os
 
 config.name = "per-test-coverage-by-lit-cfg"
 config.suffixes = [".py"]
-config.test_format = lit.formats.ShTest(execute_external=True)
+config.test_format = lit.formats.ShTest(
+    execute_external=eval(lit_config.params.get("execute_external")),
+    preamble_commands=["%{python} %s | FileCheck -DINDEX=0 %s"]
+)
 lit_config.per_test_coverage = True
 config.substitutions.append(("%{python}", '"%s"' % (sys.executable)))
diff --git a/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py b/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py
index 0abe00bdedb1f28..a8790f74f178c91 100644
--- a/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py
+++ b/llvm/utils/lit/tests/Inputs/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py
@@ -1,5 +1,6 @@
 # Check that the environment variable is set correctly
-# RUN: %{python} %s | FileCheck %s
+# RUN: %{python} %s | FileCheck -DINDEX=1 %s
+# RUN: %{python} %s | FileCheck -DINDEX=2 %s
 
 # Python script to read the environment variable
 # and print its value
@@ -8,4 +9,4 @@
 llvm_profile_file = os.environ.get('LLVM_PROFILE_FILE')
 print(llvm_profile_file)
 
-# CHECK: per-test-coverage-by-lit-cfg0.profraw
+# CHECK: per-test-coverage-by-lit-cfg[[INDEX]].profraw
diff --git a/llvm/utils/lit/tests/Inputs/per-test-coverage/lit.cfg b/llvm/utils/lit/tests/Inputs/per-test-coverage/lit.cfg
index 6e7f4c0e3bc97d7..9ffca93def73fdb 100644
--- a/llvm/utils/lit/tests/Inputs/per-test-coverage/lit.cfg
+++ b/llvm/utils/lit/tests/Inputs/per-test-coverage/lit.cfg
@@ -3,6 +3,8 @@ import os
 
 config.name = "per-test-coverage"
 config.suffixes = [".py"]
-config.test_format = lit.formats.ShTest(execute_external=True)
+config.test_format = lit.formats.ShTest(
+    execute_external=eval(lit_config.params.get("execute_external")),
+    preamble_commands=["%{python} %s | FileCheck -DINDEX=0 %s"]
+)
 config.substitutions.append(("%{python}", '"%s"' % (sys.executable)))
-
diff --git a/llvm/utils/lit/tests/Inputs/per-test-coverage/per-test-coverage.py b/llvm/utils/lit/tests/Inputs/per-test-coverage/per-test-coverage.py
index 65e4aa69bff079f..59e806939a23acb 100644
--- a/llvm/utils/lit/tests/Inputs/per-test-coverage/per-test-coverage.py
+++ b/llvm/utils/lit/tests/Inputs/per-test-coverage/per-test-coverage.py
@@ -1,5 +1,6 @@
 # Check that the environment variable is set correctly
-# RUN: %{python} %s | FileCheck %s
+# RUN: %{python} %s | FileCheck -DINDEX=1 %s
+# RUN: %{python} %s | FileCheck -DINDEX=2 %s
 
 # Python script to read the environment variable
 # and print its value
@@ -8,4 +9,4 @@
 llvm_profile_file = os.environ.get('LLVM_PROFILE_FILE')
 print(llvm_profile_file)
 
-# CHECK: per-test-coverage0.profraw
+# CHECK: per-test-coverage[[INDEX]].profraw
diff --git a/llvm/utils/lit/tests/allow-retries.py b/llvm/utils/lit/tests/allow-retries.py
index b8abe0ba4fee169..bf6c041933887a3 100644
--- a/llvm/utils/lit/tests/allow-retries.py
+++ b/llvm/utils/lit/tests/allow-retries.py
@@ -39,3 +39,15 @@
 # RUN: rm -f %t.counter
 # RUN: %{lit} %{inputs}/test_retry_attempts/test.py -Dcounter=%t.counter -Dpython=%{python} | FileCheck --check-prefix=CHECK-TEST6 %s
 # CHECK-TEST6: Passed With Retry: 1
+
+# This test checks that --per-test-coverage doesn't accumulate inserted
+# LLVM_PROFILE_FILE= commands across retries.
+#
+# RUN: rm -f %t.counter
+# RUN: %{lit} -a %{inputs}/test_retry_attempts/test.py --per-test-coverage\
+# RUN:     -Dcounter=%t.counter -Dpython=%{python} | \
+# RUN:   FileCheck --check-prefix=CHECK-TEST7 %s
+#     CHECK-TEST7: Command Output (stdout):
+#     CHECK-TEST7: LLVM_PROFILE_FILE=
+# CHECK-TEST7-NOT: LLVM_PROFILE_FILE=
+#     CHECK-TEST7: Passed With Retry: 1
diff --git a/llvm/utils/lit/tests/per-test-coverage-by-lit-cfg.py b/llvm/utils/lit/tests/per-test-coverage-by-lit-cfg.py
index 1cc3927dc89041c..189c1cebd623b4c 100644
--- a/llvm/utils/lit/tests/per-test-coverage-by-lit-cfg.py
+++ b/llvm/utils/lit/tests/per-test-coverage-by-lit-cfg.py
@@ -1,6 +1,24 @@
 # Test if lit_config.per_test_coverage in lit.cfg sets individual test case coverage.
 
-# RUN: %{lit} -a -v %{inputs}/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py \
-# RUN: | FileCheck -match-full-lines %s
-#
-# CHECK: PASS: per-test-coverage-by-lit-cfg :: per-test-coverage-by-lit-cfg.py ({{[^)]*}})
+# RUN: %{lit} -a -vv -Dexecute_external=False \
+# RUN:     %{inputs}/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py | \
+# RUN:   FileCheck -DOUT=stdout %s
+
+# RUN: %{lit} -a -vv -Dexecute_external=True \
+# RUN:     %{inputs}/per-test-coverage-by-lit-cfg/per-test-coverage-by-lit-cfg.py | \
+# RUN:   FileCheck -DOUT=stderr %s
+
+#      CHECK: {{^}}PASS: per-test-coverage-by-lit-cfg :: per-test-coverage-by-lit-cfg.py ({{[^)]*}})
+#      CHECK: Command Output ([[OUT]]):
+# CHECK-NEXT: --
+#      CHECK: export
+#      CHECK: LLVM_PROFILE_FILE=per-test-coverage-by-lit-cfg0.profraw
+#      CHECK: per-test-coverage-by-lit-cfg.py
+#      CHECK: {{RUN}}: at line 2
+#      CHECK: export
+#      CHECK: LLVM_PROFILE_FILE=per-test-coverage-by-lit-cfg1.profraw
+#      CHECK: per-test-coverage-by-lit-cfg.py
+#      CHECK: {{RUN}}: at line 3
+#      CHECK: export
+#      CHECK: LLVM_PROFILE_FILE=per-test-coverage-by-lit-cfg2.profraw
+#      CHECK: per-test-coverage-by-lit-cfg.py
diff --git a/llvm/utils/lit/tests/per-test-coverage.py b/llvm/utils/lit/tests/per-test-coverage.py
index 428712e47925760..53c6ae99c594ffd 100644
--- a/llvm/utils/lit/tests/per-test-coverage.py
+++ b/llvm/utils/lit/tests/per-test-coverage.py
@@ -1,6 +1,25 @@
 # Test LLVM_PROFILE_FILE is set when --per-test-coverage is passed to command line.
 
-# RUN: %{lit} -a -v --per-test-coverage %{inputs}/per-test-coverage/per-test-coverage.py \
-# RUN: | FileCheck -match-full-lines %s
-#
-# CHECK: PASS: per-test-coverage :: per-test-coverage.py ({{[^)]*}})
+# RUN: %{lit} -a -vv --per-test-coverage -Dexecute_external=False \
+# RUN:     %{inputs}/per-test-coverage/per-test-coverage.py | \
+# RUN:   FileCheck -DOUT=stdout %s
+
+# RUN: %{lit} -a -vv --per-test-coverage -Dexecute_external=True \
+# RUN:        %{inputs}/per-test-coverage/per-test-coverage.py | \
+# RUN:   FileCheck -DOUT=stderr %s
+
+# CHECK: {{^}}PASS: per-test-coverage :: per-test-coverage.py ({{[^)]*}})
+t-cfg.py ({{[^)]*}})
+#      CHECK: Command Output ([[OUT]]):
+# CHECK-NEXT: --
+#      CHECK: export
+#      CHECK: LLVM_PROFILE_FILE=per-test-coverage0.profraw
+#      CHECK: per-test-coverage.py
+#      CHECK: {{RUN}}: at line 2
+#      CHECK: export
+#      CHECK: LLVM_PROFILE_FILE=per-test-coverage1.profraw
+#      CHECK: per-test-coverage.py
+#      CHECK: {{RUN}}: at line 3
+#      CHECK: export
+#      CHECK: LLVM_PROFILE_FILE=per-test-coverage2.profraw
+#      CHECK: per-test-coverage.py

>From e756b3ac745a8938e608c749a05e28f3b12b5252 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Mon, 11 Sep 2023 12:06:19 -0400
Subject: [PATCH 2/2] Remove garbage from test that breaks black

---
 llvm/utils/lit/tests/per-test-coverage.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/utils/lit/tests/per-test-coverage.py b/llvm/utils/lit/tests/per-test-coverage.py
index 53c6ae99c594ffd..cf5e82c44dc51a7 100644
--- a/llvm/utils/lit/tests/per-test-coverage.py
+++ b/llvm/utils/lit/tests/per-test-coverage.py
@@ -8,8 +8,7 @@
 # RUN:        %{inputs}/per-test-coverage/per-test-coverage.py | \
 # RUN:   FileCheck -DOUT=stderr %s
 
-# CHECK: {{^}}PASS: per-test-coverage :: per-test-coverage.py ({{[^)]*}})
-t-cfg.py ({{[^)]*}})
+#      CHECK: {{^}}PASS: per-test-coverage :: per-test-coverage.py ({{[^)]*}})
 #      CHECK: Command Output ([[OUT]]):
 # CHECK-NEXT: --
 #      CHECK: export



More information about the llvm-commits mailing list