Skip to content

Commit 325d560

Browse files
Redesign to support verification that a test file has been correctly
converted to 3c-regtest. `llvm-lit b9_allsafestructp.c` passes, and `./3c-regtest-unconvert.py b9_allsafestructp.c` prints its content as of the test-command-refactoring.base branch.
1 parent 3225c31 commit 325d560

File tree

4 files changed

+133
-49
lines changed

4 files changed

+133
-49
lines changed

clang/test/3C/3c-regtest-unconvert.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python
2+
#
3+
# 3c-regtest-unconvert.py can be used to verify that an existing 3C regression
4+
# test with a written-out RUN script has been correctly converted to use
5+
# 3c-regtest.py.
6+
#
7+
# Usage: 3c-regtest-unconvert.py TEST_FILE
8+
#
9+
# This verifies that the TEST_FILE contains a 3c-regtest.py RUN command in
10+
# canonical form and then prints the TEST_FILE with this RUN command replaced by
11+
# the generated RUN script. The output should be identical to the test file
12+
# before conversion to 3c-regtest.py.
13+
14+
import sys
15+
import re
16+
import argparse
17+
18+
import script_generator
19+
20+
parser = argparse.ArgumentParser(description='''\
21+
Converts a 3C regression test that uses 3c-regtest.py back to one with a
22+
written-out RUN script so you can verify that a test was correctly converted to
23+
use 3c-regtest.py.\
24+
''')
25+
# TODO: Add help
26+
parser.add_argument('test_file')
27+
28+
argobj = parser.parse_args()
29+
30+
with open(argobj.test_file) as f:
31+
lines = f.readlines()
32+
33+
m = re.search(r"\A// RUN: %S/3c-regtest\.py (.*) %s -t %t --clang '%clang'\n\Z", lines[0])
34+
if m is None:
35+
sys.exit('The first line of %s is not a canonical 3c-regtest.py RUN line.' % argobj.test_file)
36+
test_type_flags_joined = m.group(1)
37+
38+
# FUTURE: Will we need to handle quoting?
39+
test_type_flags = test_type_flags_joined.split(' ')
40+
# XXX: This just exits on error. We'd like to add a more meaningful message, but
41+
# the default Python version on gamera (2.7.18) is too old to support
42+
# exit_on_error=False.
43+
test_type_argobj = script_generator.parser.parse_args(test_type_flags + [argobj.test_file])
44+
45+
run_lines = ['// RUN: %s\n' % cmd for cmd in script_generator.generate_commands(test_type_argobj)]
46+
new_lines = run_lines + lines[1:]
47+
sys.stdout.write(''.join(new_lines))

clang/test/3C/3c-regtest.py

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,52 @@
11
#!/usr/bin/env python
22
#
3-
# Usage: 3c-regtest -t TMPNAME [options...] SRC_FILE
3+
# 3c-regtest.py: Run a 3C regression test using a standard RUN script.
44
#
5-
# TMPNAME corresponds to %t and SRC_FILE corresponds to %s. Both are required.
5+
# 3c-regtest.py is intended to be invoked by a single RUN command in the test
6+
# file. The canonical form is:
67
#
7-
# --subst can be used for things such as %clang, for example (assuming single
8-
# --quotes are removed by the shell):
8+
# // RUN: %S/3c-regtest.py TEST_TYPE_FLAGS %s -t %t --clang '%clang'
99
#
10-
# --subst %clang 'clang -some-flag'
10+
# 3c-regtest.py generates a RUN script based on the TEST_TYPE_FLAGS (using
11+
# script_generator.py) and runs it using code from `lit`.
1112
#
12-
# (Note: A literal % has to be represented as %% in a RUN line. If we instead
13-
# established the convention of automatically prepending the % here, then the
14-
# RUN line would trip the "Do not use 'clang' in tests, use '%clang'." error.)
15-
#
16-
# Example RUN line:
17-
#
18-
# // RUN: %S/3c-regtest.py -t %t --subst %%clang '%clang' %s
19-
#
20-
# Soon, we'll add options for different kinds of 3C regression tests.
13+
# The -t and --clang flags are used to pass substitution values from the outer
14+
# `lit` configuration so that 3c-regtest.py knows what to substitute for
15+
# occurrences of %t and %clang in its script. We'll add flags like this for all
16+
# % codes that appear in the scripts.
2117

22-
# TODO: Add Windows compatibility code once we have an easy way to test on Windows.
18+
# TODO: Add Windows compatibility code once we have an easy way to test on
19+
# Windows.
2320

2421
import sys
2522
import os
2623
import platform
2724
import argparse
2825

26+
import script_generator
27+
2928
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) +
3029
'/../../../llvm/utils/lit')
3130
import lit.TestRunner
3231

33-
print "NOTICE: cwd is %s" % os.getcwd()
34-
35-
def die(msg):
36-
sys.stderr.write('Error: %s\n' % msg)
37-
sys.exit(1)
32+
parser = argparse.ArgumentParser(description='Run a 3C regression test.',
33+
parents=[script_generator.parser])
34+
# Substitution arguments. The test file name is already in
35+
# script_generator.parser.
36+
parser.add_argument('-t')
37+
parser.add_argument('--clang')
3838

39-
parser = argparse.ArgumentParser(description='Run a 3C regression test.')
40-
# TODO: Add help
41-
parser.add_argument('test_file')
42-
parser.add_argument('-t', required=True)
43-
parser.add_argument('--subst', action='append', nargs=2, default=[])
44-
args = parser.parse_args()
39+
argobj = parser.parse_args()
4540

46-
test_dir = os.path.dirname(args.test_file)
47-
if test_dir == '':
48-
test_dir = '.'
41+
test_dir = os.path.dirname(os.path.abspath(argobj.test_file))
4942

50-
tmpName = args.t
51-
tmpNameSuffix = '.tmp'
52-
if tmpName.endswith(tmpNameSuffix):
53-
tmpBase = tmpName[:-len(tmpNameSuffix)]
54-
else:
55-
die('-t argument %s does not end with %s' % (tmpName, tmpNameSuffix))
43+
tmpName = argobj.t
44+
tmpBase = script_generator.remove_suffix(tmpName, '.tmp')
45+
if tmpBase is None:
46+
sys.exit('-t argument %s does not end with .tmp' % tmpName)
5647

48+
# `lit` supports more substitutions, but these are the only ones needed by the
49+
# tests that use 3c-regtest.py so far.
5750
substitutions = [
5851
# #_MARKER_# is a hack copied from getDefaultSubstitutions in
5952
# llvm/utils/lit/lit/TestRunner.py. To explain it a bit more fully:
@@ -66,23 +59,14 @@ def die(msg):
6659
# over the input from left to right, replacing codes as they are found, but
6760
# apparently that wasn't worth the extra code in `lit`.
6861
('%%', '#_MARKER_#'),
69-
('%s', args.test_file),
62+
('%s', argobj.test_file),
7063
('%S', test_dir),
7164
('%t', tmpName),
65+
('%clang', argobj.clang),
66+
('#_MARKER_#', '%')
7267
]
73-
substitutions.extend(args.subst)
74-
substitutions.append(('#_MARKER_#', '%'))
7568

76-
# Starting with processor.py because it's always the same.
77-
commands = [
78-
# FIXME: 'foo.c' + 'hecked.c' is a terrible hack; find the right way to do this.
79-
'3c -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s',
80-
'3c -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s',
81-
'3c -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null -',
82-
'3c -output-postfix=checked -alltypes %s',
83-
'3c -alltypes %shecked.c -- | count 0',
84-
'rm %shecked.c',
85-
]
69+
commands = script_generator.generate_commands(argobj)
8670
commands = lit.TestRunner.applySubstitutions(commands, substitutions)
8771

8872
class FakeTestConfig:

clang/test/3C/b9_allsafestructp.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %S/3c-regtest.py -t %t --subst %%clang '%clang' %s
1+
// RUN: %S/3c-regtest.py --predefined-script processor %s -t %t --clang '%clang'
22
#include <stddef.h>
33
#include <stddef.h>
44
extern _Itype_for_any(T) void *calloc(size_t nmemb, size_t size) : itype(_Array_ptr<T>) byte_count(nmemb * size);

clang/test/3C/script_generator.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# script_generator.py: Generates an unexpanded RUN script for a test file based
2+
# on its name and flags that specify the kind of testing to be done. Used by
3+
# both 3c-regtest.py and 3c-regtest-unconvert.py.
4+
5+
import os
6+
import re
7+
import argparse
8+
9+
def remove_suffix(string, suffix):
10+
return string[:-len(suffix)] if string.endswith(suffix) else None
11+
12+
parser = argparse.ArgumentParser(
13+
# This will be shown as if it is a program name runnable by the user. It
14+
# isn't, but give the user a clue what this argument parser represents.
15+
prog='<3c-regtest script generator>',
16+
# Without this, the parser in 3c-regtest.py raises an error about
17+
# conflicting --help options. That's silly; one would expect argparse to
18+
# handle this specially.
19+
add_help=False
20+
)
21+
# TODO: Add help
22+
parser.add_argument('test_file')
23+
# Future: Redesign in terms of kinds of testing rather than merely selecting one
24+
# of the existing 5 scripts of the test updating programs.
25+
parser.add_argument('--predefined-script', required=True,
26+
choices=['processor'])
27+
28+
# Unlike all the other % codes that are substituted by `lit`, %B is a special
29+
# code that generate_script (below) replaces with the basename of the test file
30+
# without the `.c` extension. This reproduces some of the scripts in existing
31+
# test files.
32+
predefined_scripts = {
33+
'processor': '''\
34+
3c -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s
35+
3c -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s
36+
3c -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null -
37+
3c -output-postfix=checked -alltypes %s
38+
3c -alltypes %S/%B.checked.c -- | count 0
39+
rm %S/%B.checked.c
40+
''',
41+
}
42+
43+
# `argobj` should be an argument object returned by `parser` or a parser based
44+
# on it. Returns a list of command strings.
45+
def generate_commands(argobj):
46+
test_file = argobj.test_file
47+
# FUTURE: If this assumption is no longer valid, update this code.
48+
test_file_basename_without_ext = remove_suffix(os.path.basename(test_file), '.c')
49+
if test_file_basename_without_ext is None:
50+
sys.exit('Test filename does not end in `.c`.')
51+
script = remove_suffix(predefined_scripts[argobj.predefined_script], '\n')
52+
script = re.sub('%B', test_file_basename_without_ext, script)
53+
return script.split('\n')

0 commit comments

Comments
 (0)