1212import tempfile
1313
1414if any (s == "--help" for s in sys .argv ):
15- print ("""Usage:
15+ print ("""Usage:
1616GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir
1717
1818This generates test cases exercising function model specifications found in specsToTest.csv
2727After test generation completes, any lines in specsToTest.csv that didn't produce tests are output.
2828If 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
3232if 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
3838try :
39- os .makedirs (sys .argv [3 ])
39+ os .makedirs (sys .argv [3 ])
4040except 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
4545resultJava = os .path .join (sys .argv [3 ], "Test.java" )
4646resultQl = os .path .join (sys .argv [3 ], "test.ql" )
4747
4848if 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
5253workDir = tempfile .mkdtemp ()
5354
5758os .makedirs (projectDir )
5859
5960try :
60- shutil .copyfile (sys .argv [2 ], os .path .join (projectDir , "pom.xml" ))
61+ shutil .copyfile (sys .argv [2 ], os .path .join (projectDir , "pom.xml" ))
6162except 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
6567commentRegex = re .compile ("^\s*(//|#)" )
68+
69+
6670def isComment (s ):
67- return commentRegex .match (s ) is not None
71+ return commentRegex .match (s ) is not None
72+
6873
6974try :
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 )]
7277except 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
7681projectTestPkgDir = os .path .join (projectDir , "src" , "main" , "java" , "test" )
7782projectTestFile = os .path .join (projectTestPkgDir , "Test.java" )
7883
7984os .makedirs (projectTestPkgDir )
8085
86+
8187def 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
8794with open (projectTestFile , "w" ) as testJava :
88- testJava .write ("package test;\n \n public class Test {\n \n " )
95+ testJava .write ("package test;\n \n public 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
100107print ("Creating project database" )
101108cmd = ["codeql" , "database" , "create" , "--language=java" , "db" ]
102- ret = subprocess .call (cmd , cwd = projectDir )
109+ ret = subprocess .call (cmd , cwd = projectDir )
103110if 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
108117print ("Creating test-generation query" )
109118queryDir = os .path .join (workDir , "query" )
110119os .makedirs (queryDir )
111120qlFile = os .path .join (queryDir , "gen.ql" )
112121with 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" )
122+ f .write ("name: test-generation-query\n version: 0.0.0\n libraryPathDependencies: codeql-java" )
114123with 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 " )
124+ f .write (
125+ "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 " )
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
119129print ("Generating tests" )
120130generatedBqrs = 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 ]
122133ret = subprocess .call (cmd )
123134if 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
127138generatedJson = 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 ]
129141ret = subprocess .call (cmd )
130142if 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
161183with open (resultJava , "w" ) as f :
162- f .write (generateOutput ["getTestCase" ]["tuples" ][0 ][0 ])
184+ f .write (generateOutput ["getTestCase" ]["tuples" ][0 ][0 ])
163185
164186scriptPath = os .path .dirname (sys .argv [0 ])
165187
188+
166189def 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
170194with 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
179204with open (os .path .join (sys .argv [3 ], "test.expected" ), "w" ):
180- pass
205+ pass
181206
182207cmd = ['codeql' , 'query' , 'format' , '-qq' , '-i' , resultQl ]
183208subprocess .call (cmd )
184209
185- shutil .rmtree (workDir )
210+ shutil .rmtree (workDir )
0 commit comments