12
12
import tempfile
13
13
14
14
if any (s == "--help" for s in sys .argv ):
15
- print ("""Usage:
16
- GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir
15
+ print ("""Usage:
16
+ GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir [--force]
17
17
18
18
This generates test cases exercising function model specifications found in specsToTest.csv
19
19
producing files Test.java, test.ql and test.expected in outdir.
22
22
Typically this means supplying a skeleton POM <dependencies> section that retrieves whatever jars
23
23
contain the needed classes.
24
24
25
+ If --force is present, existing files may be overwritten.
26
+
25
27
Requirements: `mvn` and `codeql` should both appear on your path.
26
28
27
29
After test generation completes, any lines in specsToTest.csv that didn't produce tests are output.
28
30
If this happens, check the spelling of class and method names, and the syntax of input and output specifications.
29
31
""" )
30
- sys .exit (0 )
32
+ sys .exit (0 )
33
+
34
+ force = False
35
+ if "--force" in sys .argv :
36
+ sys .argv .remove ("--force" )
37
+ force = True
31
38
32
39
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 )
40
+ print (
41
+ "Usage: GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir [--force]" , file = sys .stderr )
42
+ print ("specsToTest.csv should contain CSV rows describing method taint-propagation specifications to test" , file = sys .stderr )
43
+ print ("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.csv" , file = sys .stderr )
44
+ sys .exit (1 )
37
45
38
46
try :
39
- os .makedirs (sys .argv [3 ])
47
+ os .makedirs (sys .argv [3 ])
40
48
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 )
49
+ if e .errno != errno .EEXIST :
50
+ print ("Failed to create output directory %s: %s" % (sys .argv [3 ], e ))
51
+ sys .exit (1 )
44
52
45
53
resultJava = os .path .join (sys .argv [3 ], "Test.java" )
46
54
resultQl = os .path .join (sys .argv [3 ], "test.ql" )
47
55
48
- 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 )
56
+ if not force and (os .path .exists (resultJava ) or os .path .exists (resultQl )):
57
+ print ("Won't overwrite existing files '%s' or '%s'" %
58
+ (resultJava , resultQl ), file = sys .stderr )
59
+ sys .exit (1 )
51
60
52
61
workDir = tempfile .mkdtemp ()
53
62
57
66
os .makedirs (projectDir )
58
67
59
68
try :
60
- shutil .copyfile (sys .argv [2 ], os .path .join (projectDir , "pom.xml" ))
69
+ shutil .copyfile (sys .argv [2 ], os .path .join (projectDir , "pom.xml" ))
61
70
except Exception as e :
62
- print ("Failed to read project POM %s: %s" % (sys .argv [2 ], e ), file = sys .stderr )
63
- sys .exit (1 )
71
+ print ("Failed to read project POM %s: %s" %
72
+ (sys .argv [2 ], e ), file = sys .stderr )
73
+ sys .exit (1 )
64
74
65
75
commentRegex = re .compile ("^\s*(//|#)" )
76
+
77
+
66
78
def isComment (s ):
67
- return commentRegex .match (s ) is not None
79
+ return commentRegex .match (s ) is not None
80
+
68
81
69
82
try :
70
- with open (sys .argv [1 ], "r" ) as f :
71
- specs = [l for l in f if not isComment (l )]
83
+ with open (sys .argv [1 ], "r" ) as f :
84
+ specs = [l for l in f if not isComment (l )]
72
85
except Exception as e :
73
- print ("Failed to open %s: %s\n " % (sys .argv [1 ], e ))
74
- sys .exit (1 )
86
+ print ("Failed to open %s: %s\n " % (sys .argv [1 ], e ))
87
+ sys .exit (1 )
75
88
76
89
projectTestPkgDir = os .path .join (projectDir , "src" , "main" , "java" , "test" )
77
90
projectTestFile = os .path .join (projectTestPkgDir , "Test.java" )
78
91
79
92
os .makedirs (projectTestPkgDir )
80
93
94
+
81
95
def qualifiedOuterNameFromCsvRow (row ):
82
- cells = row .split (";" )
83
- if len (cells ) < 2 :
84
- return None
85
- return cells [0 ] + "." + cells [1 ].replace ("$" , "." )
96
+ cells = row .split (";" )
97
+ if len (cells ) < 2 :
98
+ return None
99
+ return cells [0 ] + "." + cells [1 ].replace ("$" , "." )
100
+
86
101
87
102
with open (projectTestFile , "w" ) as testJava :
88
- testJava .write ("package test;\n \n public class Test {\n \n " )
103
+ testJava .write ("package test;\n \n public class Test {\n \n " )
89
104
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 ))
105
+ for i , spec in enumerate (specs ):
106
+ outerName = qualifiedOuterNameFromCsvRow (spec )
107
+ if outerName is None :
108
+ print ("A taint specification has the wrong format: should be 'package;classname;methodname....'" , file = sys .stderr )
109
+ print ("Mis-formatted row: " + spec , file = sys .stderr )
110
+ sys .exit (1 )
111
+ testJava .write ("\t %s obj%d = null;\n " % (outerName , i ))
97
112
98
- testJava .write ("}" )
113
+ testJava .write ("}" )
99
114
100
115
print ("Creating project database" )
101
116
cmd = ["codeql" , "database" , "create" , "--language=java" , "db" ]
102
- ret = subprocess .call (cmd , cwd = projectDir )
117
+ ret = subprocess .call (cmd , cwd = projectDir )
103
118
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 )
119
+ 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." % (
120
+ sys .argv [2 ], sys .argv [1 ]), file = sys .stderr )
121
+ print ("Failed command was: %s (cwd: %s)" %
122
+ (shlex .join (cmd ), projectDir ), file = sys .stderr )
123
+ sys .exit (1 )
107
124
108
125
print ("Creating test-generation query" )
109
126
queryDir = os .path .join (workDir , "query" )
110
127
os .makedirs (queryDir )
111
128
qlFile = os .path .join (queryDir , "gen.ql" )
112
129
with open (os .path .join (queryDir , "qlpack.yml" ), "w" ) as f :
113
- f .write ("name: test-generation-query\n version: 0.0.0\n libraryPathDependencies: codeql-java" )
130
+ f .write ("name: test-generation-query\n version: 0.0.0\n libraryPathDependencies: codeql-java" )
114
131
with open (qlFile , "w" ) as f :
115
- f .write ("import java\n import utils.GenerateFlowTestCase\n \n class GenRow extends TargetSummaryModelCsv {\n \n \t override predicate row(string r) {\n \t \t r = [\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 " )
132
+ f .write (
133
+ "import java\n import utils.GenerateFlowTestCase\n \n class GenRow extends TargetSummaryModelCsv {\n \n \t override predicate row(string r) {\n \t \t r = [\n " )
134
+ f .write (",\n " .join ('\t \t \t "%s"' % spec .strip () for spec in specs ))
135
+ f .write ("\n \t \t ]\n \t }\n }\n " )
118
136
119
137
print ("Generating tests" )
120
138
generatedBqrs = os .path .join (queryDir , "out.bqrs" )
121
- cmd = ['codeql' , 'query' , 'run' , qlFile , '--database' , os .path .join (projectDir , "db" ), '--output' , generatedBqrs ]
139
+ cmd = ['codeql' , 'query' , 'run' , qlFile , '--database' ,
140
+ os .path .join (projectDir , "db" ), '--output' , generatedBqrs ]
122
141
ret = subprocess .call (cmd )
123
142
if ret != 0 :
124
- print ("Failed to generate tests. Failed command was: " + shlex .join (cmd ))
125
- sys .exit (1 )
143
+ print ("Failed to generate tests. Failed command was: " + shlex .join (cmd ))
144
+ sys .exit (1 )
126
145
127
146
generatedJson = os .path .join (queryDir , "out.json" )
128
- cmd = ['codeql' , 'bqrs' , 'decode' , generatedBqrs , '--format=json' , '--output' , generatedJson ]
147
+ cmd = ['codeql' , 'bqrs' , 'decode' , generatedBqrs ,
148
+ '--format=json' , '--output' , generatedJson ]
129
149
ret = subprocess .call (cmd )
130
150
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 )
151
+ print ("Failed to decode BQRS. Failed command was: " + shlex .join (cmd ))
137
152
sys .exit (1 )
138
- return jsonResult [queryName ]["tuples" ]
139
153
140
- with open (generatedJson , "r" ) as f :
141
- generateOutput = json .load (f )
142
- expectedTables = ("getTestCase" , "getASupportMethodModel" , "missingSummaryModelCsv" , "getAParseFailure" )
143
154
144
- testCaseRows , supportModelRows , missingSummaryModelCsvRows , parseFailureRows = \
145
- tuple ([getTuples (k , generateOutput , generatedJson ) for k in expectedTables ])
155
+ def getTuples (queryName , jsonResult , fname ):
156
+ if queryName not in jsonResult or "tuples" not in jsonResult [queryName ]:
157
+ print ("Failed to read generated tests: expected key '%s' with a 'tuples' subkey in file '%s'" % (
158
+ queryName , fname ), file = sys .stderr )
159
+ sys .exit (1 )
160
+ return jsonResult [queryName ]["tuples" ]
146
161
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 )
153
162
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 )
163
+ with open (generatedJson , "r" ) as f :
164
+ generateOutput = json .load (f )
165
+ expectedTables = ("getTestCase" , "getASupportMethodModel" ,
166
+ "missingSummaryModelCsv" , "getAParseFailure" , "noTestCaseGenerated" )
167
+
168
+ testCaseRows , supportModelRows , missingSummaryModelCsvRows , parseFailureRows , noTestCaseGeneratedRows = \
169
+ tuple ([getTuples (k , generateOutput , generatedJson )
170
+ for k in expectedTables ])
171
+
172
+ if len (testCaseRows ) != 1 or len (testCaseRows [0 ]) != 1 :
173
+ print ("Expected exactly one getTestCase result with one column (got: %s)" %
174
+ json .dumps (testCaseRows ), file = sys .stderr )
175
+ if any (len (row ) != 1 for row in supportModelRows ):
176
+ print ("Expected exactly one column in getASupportMethodModel relation (got: %s)" %
177
+ json .dumps (supportModelRows ), file = sys .stderr )
178
+ if any (len (row ) != 2 for row in parseFailureRows ):
179
+ print ("Expected exactly two columns in parseFailureRows relation (got: %s)" %
180
+ json .dumps (parseFailureRows ), file = sys .stderr )
181
+ if any (len (row ) != 1 for row in noTestCaseGeneratedRows ):
182
+ print ("Expected exactly one column in noTestCaseGenerated relation (got: %s)" %
183
+ json .dumps (noTestCaseGeneratedRows ), file = sys .stderr )
184
+
185
+ if len (missingSummaryModelCsvRows ) != 0 :
186
+ print ("Tests for some CSV rows were requested that were not in scope (SummaryModelCsv.row does not hold):\n " +
187
+ "\n " .join (r [0 ] for r in missingSummaryModelCsvRows ))
188
+ sys .exit (1 )
189
+ if len (parseFailureRows ) != 0 :
190
+ print ("The following rows failed to generate any test case. Check package, class and method name spelling, and argument and result specifications:\n %s" %
191
+ "\n " .join (r [0 ] + ": " + r [1 ] for r in parseFailureRows ), file = sys .stderr )
192
+ sys .exit (1 )
193
+ if len (noTestCaseGeneratedRows ) != 0 :
194
+ print ("The following CSV rows failed to generate any test case due to a limitation of the query. Other test cases will still be generated:\n " +
195
+ "\n " .join (r [0 ] for r in noTestCaseGeneratedRows ))
160
196
161
197
with open (resultJava , "w" ) as f :
162
- f .write (generateOutput ["getTestCase" ]["tuples" ][0 ][0 ])
198
+ f .write (generateOutput ["getTestCase" ]["tuples" ][0 ][0 ])
163
199
164
200
scriptPath = os .path .dirname (sys .argv [0 ])
165
201
202
+
166
203
def copyfile (fromName , toFileHandle ):
167
- with open (os .path .join (scriptPath , fromName ), "r" ) as fromFileHandle :
168
- shutil .copyfileobj (fromFileHandle , toFileHandle )
204
+ with open (os .path .join (scriptPath , fromName ), "r" ) as fromFileHandle :
205
+ shutil .copyfileobj (fromFileHandle , toFileHandle )
206
+
169
207
170
208
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 )
209
+ copyfile ("testHeader.qlfrag" , f )
210
+ if len (supportModelRows ) != 0 :
211
+ copyfile ("testModelsHeader.qlfrag" , f )
212
+ f .write (", " .join ('"%s"' %
213
+ modelSpecRow [0 ].strip () for modelSpecRow in supportModelRows ))
214
+ copyfile ("testModelsFooter.qlfrag" , f )
215
+ copyfile ("testFooter.qlfrag" , f )
177
216
178
217
# Make an empty .expected file, since this is an inline-exectations test
179
218
with open (os .path .join (sys .argv [3 ], "test.expected" ), "w" ):
180
- pass
219
+ pass
181
220
182
221
cmd = ['codeql' , 'query' , 'format' , '-qq' , '-i' , resultQl ]
183
222
subprocess .call (cmd )
184
223
185
- shutil .rmtree (workDir )
224
+ shutil .rmtree (workDir )
0 commit comments