Skip to content

Commit 2036aa1

Browse files
Format test generator
1 parent aa8fa26 commit 2036aa1

File tree

1 file changed

+105
-80
lines changed

1 file changed

+105
-80
lines changed

java/ql/src/utils/GenerateFlowTestCase.py

Lines changed: 105 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import tempfile
1313

1414
if any(s == "--help" for s in sys.argv):
15-
print("""Usage:
15+
print("""Usage:
1616
GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir
1717
1818
This generates test cases exercising function model specifications found in specsToTest.csv
@@ -27,27 +27,28 @@
2727
After test generation completes, any lines in specsToTest.csv that didn't produce tests are output.
2828
If this happens, check the spelling of class and method names, and the syntax of input and output specifications.
2929
""")
30-
sys.exit(0)
30+
sys.exit(0)
3131

3232
if len(sys.argv) != 4:
33-
print("Usage: GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir", file=sys.stderr)
34-
print("specsToTest.csv should contain CSV rows describing method taint-propagation specifications to test", file=sys.stderr)
35-
print("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.csv", file=sys.stderr)
36-
sys.exit(1)
33+
print("Usage: GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir", file=sys.stderr)
34+
print("specsToTest.csv should contain CSV rows describing method taint-propagation specifications to test", file=sys.stderr)
35+
print("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.csv", file=sys.stderr)
36+
sys.exit(1)
3737

3838
try:
39-
os.makedirs(sys.argv[3])
39+
os.makedirs(sys.argv[3])
4040
except Exception as e:
41-
if e.errno != errno.EEXIST:
42-
print("Failed to create output directory %s: %s" % (sys.argv[3], e))
43-
sys.exit(1)
41+
if e.errno != errno.EEXIST:
42+
print("Failed to create output directory %s: %s" % (sys.argv[3], e))
43+
sys.exit(1)
4444

4545
resultJava = os.path.join(sys.argv[3], "Test.java")
4646
resultQl = os.path.join(sys.argv[3], "test.ql")
4747

4848
if os.path.exists(resultJava) or os.path.exists(resultQl):
49-
print("Won't overwrite existing files '%s' or '%s'" % (resultJava, resultQl), file = sys.stderr)
50-
sys.exit(1)
49+
print("Won't overwrite existing files '%s' or '%s'" %
50+
(resultJava, resultQl), file=sys.stderr)
51+
sys.exit(1)
5152

5253
workDir = tempfile.mkdtemp()
5354

@@ -57,129 +58,153 @@
5758
os.makedirs(projectDir)
5859

5960
try:
60-
shutil.copyfile(sys.argv[2], os.path.join(projectDir, "pom.xml"))
61+
shutil.copyfile(sys.argv[2], os.path.join(projectDir, "pom.xml"))
6162
except Exception as e:
62-
print("Failed to read project POM %s: %s" % (sys.argv[2], e), file = sys.stderr)
63-
sys.exit(1)
63+
print("Failed to read project POM %s: %s" %
64+
(sys.argv[2], e), file=sys.stderr)
65+
sys.exit(1)
6466

6567
commentRegex = re.compile("^\s*(//|#)")
68+
69+
6670
def isComment(s):
67-
return commentRegex.match(s) is not None
71+
return commentRegex.match(s) is not None
72+
6873

6974
try:
70-
with open(sys.argv[1], "r") as f:
71-
specs = [l for l in f if not isComment(l)]
75+
with open(sys.argv[1], "r") as f:
76+
specs = [l for l in f if not isComment(l)]
7277
except Exception as e:
73-
print("Failed to open %s: %s\n" % (sys.argv[1], e))
74-
sys.exit(1)
78+
print("Failed to open %s: %s\n" % (sys.argv[1], e))
79+
sys.exit(1)
7580

7681
projectTestPkgDir = os.path.join(projectDir, "src", "main", "java", "test")
7782
projectTestFile = os.path.join(projectTestPkgDir, "Test.java")
7883

7984
os.makedirs(projectTestPkgDir)
8085

86+
8187
def qualifiedOuterNameFromCsvRow(row):
82-
cells = row.split(";")
83-
if len(cells) < 2:
84-
return None
85-
return cells[0] + "." + cells[1].replace("$", ".")
88+
cells = row.split(";")
89+
if len(cells) < 2:
90+
return None
91+
return cells[0] + "." + cells[1].replace("$", ".")
92+
8693

8794
with open(projectTestFile, "w") as testJava:
88-
testJava.write("package test;\n\npublic class Test {\n\n")
95+
testJava.write("package test;\n\npublic class Test {\n\n")
8996

90-
for i, spec in enumerate(specs):
91-
outerName = qualifiedOuterNameFromCsvRow(spec)
92-
if outerName is None:
93-
print("A taint specification has the wrong format: should be 'package;classname;methodname....'", file = sys.stderr)
94-
print("Mis-formatted row: " + spec, file = sys.stderr)
95-
sys.exit(1)
96-
testJava.write("\t%s obj%d = null;\n" % (outerName, i))
97+
for i, spec in enumerate(specs):
98+
outerName = qualifiedOuterNameFromCsvRow(spec)
99+
if outerName is None:
100+
print("A taint specification has the wrong format: should be 'package;classname;methodname....'", file=sys.stderr)
101+
print("Mis-formatted row: " + spec, file=sys.stderr)
102+
sys.exit(1)
103+
testJava.write("\t%s obj%d = null;\n" % (outerName, i))
97104

98-
testJava.write("}")
105+
testJava.write("}")
99106

100107
print("Creating project database")
101108
cmd = ["codeql", "database", "create", "--language=java", "db"]
102-
ret = subprocess.call(cmd, cwd = projectDir)
109+
ret = subprocess.call(cmd, cwd=projectDir)
103110
if ret != 0:
104-
print("Failed to create project database. Check that '%s' is a valid POM that pulls in all necessary dependencies, and '%s' specifies valid classes and methods." % (sys.argv[2], sys.argv[1]), file = sys.stderr)
105-
print("Failed command was: %s (cwd: %s)" % (shlex.join(cmd), projectDir), file = sys.stderr)
106-
sys.exit(1)
111+
print("Failed to create project database. Check that '%s' is a valid POM that pulls in all necessary dependencies, and '%s' specifies valid classes and methods." % (
112+
sys.argv[2], sys.argv[1]), file=sys.stderr)
113+
print("Failed command was: %s (cwd: %s)" %
114+
(shlex.join(cmd), projectDir), file=sys.stderr)
115+
sys.exit(1)
107116

108117
print("Creating test-generation query")
109118
queryDir = os.path.join(workDir, "query")
110119
os.makedirs(queryDir)
111120
qlFile = os.path.join(queryDir, "gen.ql")
112121
with open(os.path.join(queryDir, "qlpack.yml"), "w") as f:
113-
f.write("name: test-generation-query\nversion: 0.0.0\nlibraryPathDependencies: codeql-java")
122+
f.write("name: test-generation-query\nversion: 0.0.0\nlibraryPathDependencies: codeql-java")
114123
with open(qlFile, "w") as f:
115-
f.write("import java\nimport utils.GenerateFlowTestCase\n\nclass GenRow extends TargetSummaryModelCsv {\n\n\toverride predicate row(string r) {\n\t\tr = [\n")
116-
f.write(",\n".join('\t\t\t"%s"' % spec.strip() for spec in specs))
117-
f.write("\n\t\t]\n\t}\n}\n")
124+
f.write(
125+
"import java\nimport utils.GenerateFlowTestCase\n\nclass GenRow extends TargetSummaryModelCsv {\n\n\toverride predicate row(string r) {\n\t\tr = [\n")
126+
f.write(",\n".join('\t\t\t"%s"' % spec.strip() for spec in specs))
127+
f.write("\n\t\t]\n\t}\n}\n")
118128

119129
print("Generating tests")
120130
generatedBqrs = os.path.join(queryDir, "out.bqrs")
121-
cmd = ['codeql', 'query', 'run', qlFile, '--database', os.path.join(projectDir, "db"), '--output', generatedBqrs]
131+
cmd = ['codeql', 'query', 'run', qlFile, '--database',
132+
os.path.join(projectDir, "db"), '--output', generatedBqrs]
122133
ret = subprocess.call(cmd)
123134
if ret != 0:
124-
print("Failed to generate tests. Failed command was: " + shlex.join(cmd))
125-
sys.exit(1)
135+
print("Failed to generate tests. Failed command was: " + shlex.join(cmd))
136+
sys.exit(1)
126137

127138
generatedJson = os.path.join(queryDir, "out.json")
128-
cmd = ['codeql', 'bqrs', 'decode', generatedBqrs, '--format=json', '--output', generatedJson]
139+
cmd = ['codeql', 'bqrs', 'decode', generatedBqrs,
140+
'--format=json', '--output', generatedJson]
129141
ret = subprocess.call(cmd)
130142
if ret != 0:
131-
print("Failed to decode BQRS. Failed command was: " + shlex.join(cmd))
132-
sys.exit(1)
133-
134-
def getTuples(queryName, jsonResult, fname):
135-
if queryName not in jsonResult or "tuples" not in jsonResult[queryName]:
136-
print("Failed to read generated tests: expected key '%s' with a 'tuples' subkey in file '%s'" % (queryName, fname), file = sys.stderr)
143+
print("Failed to decode BQRS. Failed command was: " + shlex.join(cmd))
137144
sys.exit(1)
138-
return jsonResult[queryName]["tuples"]
139145

140-
with open(generatedJson, "r") as f:
141-
generateOutput = json.load(f)
142-
expectedTables = ("getTestCase", "getASupportMethodModel", "missingSummaryModelCsv", "getAParseFailure")
143146

144-
testCaseRows, supportModelRows, missingSummaryModelCsvRows, parseFailureRows = \
145-
tuple([getTuples(k, generateOutput, generatedJson) for k in expectedTables])
147+
def getTuples(queryName, jsonResult, fname):
148+
if queryName not in jsonResult or "tuples" not in jsonResult[queryName]:
149+
print("Failed to read generated tests: expected key '%s' with a 'tuples' subkey in file '%s'" % (
150+
queryName, fname), file=sys.stderr)
151+
sys.exit(1)
152+
return jsonResult[queryName]["tuples"]
146153

147-
if len(testCaseRows) != 1 or len(testCaseRows[0]) != 1:
148-
print("Expected exactly one getTestCase result with one column (got: %s)" % json.dumps(testCaseRows), file = sys.stderr)
149-
if any(len(row) != 1 for row in supportModelRows):
150-
print("Expected exactly one column in getASupportMethodModel relation (got: %s)" % json.dumps(supportModelRows), file = sys.stderr)
151-
if any(len(row) != 2 for row in parseFailureRows):
152-
print("Expected exactly two columns in parseFailureRows relation (got: %s)" % json.dumps(parseFailureRows), file = sys.stderr)
153154

154-
if len(missingSummaryModelCsvRows) != 0:
155-
print("Tests for some CSV rows were requested that were not in scope (SummaryModelCsv.row does not hold):\n" + "\n".join(r[0] for r in missingSummaryModelCsvRows))
156-
sys.exit(1)
157-
if len(parseFailureRows) != 0:
158-
print("The following rows failed to generate any test case. Check package, class and method name spelling, and argument and result specifications:\n%s" % "\n".join(r[0] + ": " + r[1] for r in parseFailureRows), file = sys.stderr)
159-
sys.exit(1)
155+
with open(generatedJson, "r") as f:
156+
generateOutput = json.load(f)
157+
expectedTables = ("getTestCase", "getASupportMethodModel",
158+
"missingSummaryModelCsv", "getAParseFailure")
159+
160+
testCaseRows, supportModelRows, missingSummaryModelCsvRows, parseFailureRows = \
161+
tuple([getTuples(k, generateOutput, generatedJson)
162+
for k in expectedTables])
163+
164+
if len(testCaseRows) != 1 or len(testCaseRows[0]) != 1:
165+
print("Expected exactly one getTestCase result with one column (got: %s)" %
166+
json.dumps(testCaseRows), file=sys.stderr)
167+
if any(len(row) != 1 for row in supportModelRows):
168+
print("Expected exactly one column in getASupportMethodModel relation (got: %s)" %
169+
json.dumps(supportModelRows), file=sys.stderr)
170+
if any(len(row) != 2 for row in parseFailureRows):
171+
print("Expected exactly two columns in parseFailureRows relation (got: %s)" %
172+
json.dumps(parseFailureRows), file=sys.stderr)
173+
174+
if len(missingSummaryModelCsvRows) != 0:
175+
print("Tests for some CSV rows were requested that were not in scope (SummaryModelCsv.row does not hold):\n" +
176+
"\n".join(r[0] for r in missingSummaryModelCsvRows))
177+
sys.exit(1)
178+
if len(parseFailureRows) != 0:
179+
print("The following rows failed to generate any test case. Check package, class and method name spelling, and argument and result specifications:\n%s" %
180+
"\n".join(r[0] + ": " + r[1] for r in parseFailureRows), file=sys.stderr)
181+
sys.exit(1)
160182

161183
with open(resultJava, "w") as f:
162-
f.write(generateOutput["getTestCase"]["tuples"][0][0])
184+
f.write(generateOutput["getTestCase"]["tuples"][0][0])
163185

164186
scriptPath = os.path.dirname(sys.argv[0])
165187

188+
166189
def copyfile(fromName, toFileHandle):
167-
with open(os.path.join(scriptPath, fromName), "r") as fromFileHandle:
168-
shutil.copyfileobj(fromFileHandle, toFileHandle)
190+
with open(os.path.join(scriptPath, fromName), "r") as fromFileHandle:
191+
shutil.copyfileobj(fromFileHandle, toFileHandle)
192+
169193

170194
with open(resultQl, "w") as f:
171-
copyfile("testHeader.qlfrag", f)
172-
if len(supportModelRows) != 0:
173-
copyfile("testModelsHeader.qlfrag", f)
174-
f.write(", ".join('"%s"' % modelSpecRow[0].strip() for modelSpecRow in supportModelRows))
175-
copyfile("testModelsFooter.qlfrag", f)
176-
copyfile("testFooter.qlfrag", f)
195+
copyfile("testHeader.qlfrag", f)
196+
if len(supportModelRows) != 0:
197+
copyfile("testModelsHeader.qlfrag", f)
198+
f.write(", ".join('"%s"' %
199+
modelSpecRow[0].strip() for modelSpecRow in supportModelRows))
200+
copyfile("testModelsFooter.qlfrag", f)
201+
copyfile("testFooter.qlfrag", f)
177202

178203
# Make an empty .expected file, since this is an inline-exectations test
179204
with open(os.path.join(sys.argv[3], "test.expected"), "w"):
180-
pass
205+
pass
181206

182207
cmd = ['codeql', 'query', 'format', '-qq', '-i', resultQl]
183208
subprocess.call(cmd)
184209

185-
shutil.rmtree(workDir)
210+
shutil.rmtree(workDir)

0 commit comments

Comments
 (0)