Skip to content

Commit 430b432

Browse files
Merge pull request github#12195 from joefarebrother/testgen-improvements
Java: Test generator improvements
2 parents 3b1b3b4 + d9e5c6c commit 430b432

File tree

3 files changed

+78
-21
lines changed

3 files changed

+78
-21
lines changed

java/ql/src/utils/flowtestcasegenerator/FlowTestCase.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class TestCase extends TTestCase {
228228
*/
229229
Type getOutputType() {
230230
if baseOutput = SummaryComponentStack::return()
231-
then result = callable.getReturnType()
231+
then result = getReturnType(callable)
232232
else
233233
exists(int i |
234234
baseOutput = SummaryComponentStack::argument(i) and

java/ql/src/utils/flowtestcasegenerator/FlowTestCaseUtils.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ Type getRootSourceDeclaration(Type t) {
1616
else result = t
1717
}
1818

19+
/** Gets the return type of the callable c, or the constructed tpe if it's a constructor */
20+
Type getReturnType(Callable c) {
21+
if c instanceof Constructor then result = c.getDeclaringType() else result = c.getReturnType()
22+
}
23+
1924
/**
2025
* Holds if type `t` does not clash with another type we want to import that has the same base name.
2126
*/

java/ql/src/utils/flowtestcasegenerator/GenerateFlowTestCase.py

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/python3
1+
#!/usr/bin/env python3
22

33
import errno
44
import json
@@ -13,11 +13,14 @@
1313

1414
if any(s == "--help" for s in sys.argv):
1515
print("""Usage:
16-
GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir [--force]
16+
GenerateFlowTestCase.py specsToTest projectPom.xml outdir [--force]
1717
18-
This generates test cases exercising function model specifications found in specsToTest.csv
18+
This generates test cases exercising function model specifications found in specsToTest
1919
producing files Test.java, test.ql, test.ext.yml and test.expected in outdir.
2020
21+
specsToTest should either be a .csv file, a .yml file, or a directory of .yml files, containing the
22+
model specifications to test.
23+
2124
projectPom.xml should be a Maven pom sufficient to resolve the classes named in specsToTest.csv.
2225
Typically this means supplying a skeleton POM <dependencies> section that retrieves whatever jars
2326
contain the needed classes.
@@ -40,14 +43,15 @@
4043

4144
if len(sys.argv) != 4:
4245
print(
43-
"Usage: GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir [--force]", file=sys.stderr)
44-
print("specsToTest.csv should contain CSV rows describing method taint-propagation specifications to test", file=sys.stderr)
45-
print("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.csv", file=sys.stderr)
46+
"Usage: GenerateFlowTestCase.py specsToTest projectPom.xml outdir [--force]", file=sys.stderr)
47+
print("specsToTest should contain CSV rows or YAML models describing method taint-propagation specifications to test", file=sys.stderr)
48+
print("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest", file=sys.stderr)
49+
print("\nRun with --help for more details.", file=sys.stderr)
4650
sys.exit(1)
4751

4852
try:
4953
os.makedirs(sys.argv[3])
50-
except Exception as e:
54+
except OSError as e:
5155
if e.errno != errno.EEXIST:
5256
print("Failed to create output directory %s: %s" % (sys.argv[3], e))
5357
sys.exit(1)
@@ -75,38 +79,86 @@
7579
(sys.argv[2], e), file=sys.stderr)
7680
sys.exit(1)
7781

78-
commentRegex = re.compile("^\s*(//|#)")
82+
commentRegex = re.compile(r"^\s*(//|#)")
7983

8084

8185
def isComment(s):
8286
return commentRegex.match(s) is not None
8387

8488

85-
try:
86-
with open(sys.argv[1], "r") as f:
87-
specs = [l for l in f if not isComment(l)]
88-
except Exception as e:
89-
print("Failed to open %s: %s\n" % (sys.argv[1], e))
89+
def readCsv(file):
90+
try:
91+
with open(file, "r") as f:
92+
specs = [l.strip() for l in f if not isComment(l)]
93+
except Exception as e:
94+
print("Failed to open %s: %s\n" % (file, e))
95+
sys.exit(1)
96+
97+
specs = [row.split(";") for row in specs]
98+
return specs
99+
100+
101+
def readYml(file):
102+
try:
103+
import yaml
104+
with open(file, "r") as f:
105+
doc = yaml.load(f.read(), yaml.Loader)
106+
specs = []
107+
for ext in doc['extensions']:
108+
if ext['addsTo']['extensible'] == 'summaryModel':
109+
for row in ext['data']:
110+
if isinstance(row[2], bool):
111+
row[2] = str(row[2]).lower()
112+
specs.append(row)
113+
return specs
114+
except ImportError:
115+
print("PyYAML not found - try \n pip install pyyaml")
116+
sys.exit(1)
117+
except ValueError as e:
118+
print("Invalid yaml model in %s: %s\n" % (file, e))
119+
sys.exit(1)
120+
except OSError as e:
121+
print("Failed to open %s: %s\n" % (file, e))
122+
sys.exit(1)
123+
124+
125+
def readYmlDir(dirname):
126+
specs = []
127+
for f in os.listdir(dirname):
128+
if f.endswith('.yml'):
129+
specs += readYml(f"{dirname}/{f}")
130+
return specs
131+
132+
133+
specsFile = sys.argv[1]
134+
if os.path.isdir(specsFile):
135+
specs = readYmlDir(specsFile)
136+
elif specsFile.endswith(".yml") or specsFile.endswith(".yaml"):
137+
specs = readYml(specsFile)
138+
elif specsFile.endswith(".csv"):
139+
specs = readCsv(specsFile)
140+
else:
141+
print(f"Invalid specs {specsFile}. Must be a csv file, a yml file, or a directory of yml files.")
90142
sys.exit(1)
91143

144+
92145
projectTestPkgDir = os.path.join(projectDir, "src", "main", "java", "test")
93146
projectTestFile = os.path.join(projectTestPkgDir, "Test.java")
94147

95148
os.makedirs(projectTestPkgDir)
96149

97150

98-
def qualifiedOuterNameFromCsvRow(row):
99-
cells = row.split(";")
100-
if len(cells) < 2:
151+
def qualifiedOuterNameFromRow(row):
152+
if len(row) < 2:
101153
return None
102-
return cells[0] + "." + cells[1].replace("$", ".")
154+
return row[0] + "." + row[1].replace("$", ".")
103155

104156

105157
with open(projectTestFile, "w") as testJava:
106158
testJava.write("package test;\n\npublic class Test {\n\n")
107159

108160
for i, spec in enumerate(specs):
109-
outerName = qualifiedOuterNameFromCsvRow(spec)
161+
outerName = qualifiedOuterNameFromRow(spec)
110162
if outerName is None:
111163
print("A taint specification has the wrong format: should be 'package;classname;methodname....'", file=sys.stderr)
112164
print("Mis-formatted row: " + spec, file=sys.stderr)
@@ -140,7 +192,7 @@ def qualifiedOuterNameFromCsvRow(row):
140192
with open(qlFile, "w") as f:
141193
f.write(
142194
"import java\nimport utils.flowtestcasegenerator.GenerateFlowTestCase\n\nclass GenRow extends TargetSummaryModelCsv {\n\n\toverride predicate row(string r) {\n\t\tr = [\n")
143-
f.write(",\n".join('\t\t\t"%s"' % spec.strip() for spec in specs))
195+
f.write(",\n".join('\t\t\t"%s"' % ';'.join(spec) for spec in specs))
144196
f.write("\n\t\t]\n\t}\n}\n")
145197

146198
print("Generating tests")
@@ -221,7 +273,7 @@ def copyfile(fromName, toFileHandle):
221273
# Make a test extension file
222274
with open(resultYml, "w") as f:
223275
models = "\n".join(' - [%s]' %
224-
modelSpecRow[0].strip() for modelSpecRow in supportModelRows)
276+
modelSpecRow[0].strip() for modelSpecRow in supportModelRows)
225277
dataextensions = f"""extensions:
226278
- addsTo:
227279
pack: codeql/java-tests

0 commit comments

Comments
 (0)