blob: 74c6d616c5934a848523b26d1dbf4b421b150838 [file] [log] [blame]
David Brazdilee690a32014-12-01 17:04:16 +00001#!/usr/bin/env python3
2#
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17
18# Checker is a testing tool which compiles a given test file and compares the
19# state of the control-flow graph before and after each optimization pass
20# against a set of assertions specified alongside the tests.
21#
22# Tests are written in Java, turned into DEX and compiled with the Optimizing
David Brazdil9a6f20e2014-12-19 11:17:21 +000023# compiler. "Check lines" are assertions formatted as comments of the Java file.
24# They begin with prefix 'CHECK' followed by a pattern that the engine attempts
25# to match in the compiler-generated output.
David Brazdilee690a32014-12-01 17:04:16 +000026#
27# Assertions are tested in groups which correspond to the individual compiler
28# passes. Each group of check lines therefore must start with a 'CHECK-START'
29# header which specifies the output group it should be tested against. The group
30# name must exactly match one of the groups recognized in the output (they can
31# be listed with the '--list-groups' command-line flag).
32#
David Brazdil9a6f20e2014-12-19 11:17:21 +000033# Matching of check lines is carried out in the order of appearance in the
34# source file. There are three types of check lines:
35# - CHECK: Must match an output line which appears in the output group
36# later than lines matched against any preceeding checks. Output
37# lines must therefore match the check lines in the same order.
38# These are referred to as "in-order" checks in the code.
39# - CHECK-DAG: Must match an output line which appears in the output group
40# later than lines matched against any preceeding in-order checks.
41# In other words, the order of output lines does not matter
42# between consecutive DAG checks.
43# - CHECK-NOT: Must not match any output line which appear in the output group
44# later than lines matched against any preceeding checks and
45# earlier than lines matched against any subsequent checks.
46# Surrounding non-negative checks (or boundaries of the group)
47# therefore create a scope within which the assertion is verified.
48#
49# Check-line patterns are treated as plain text rather than regular expressions
David Brazdilee690a32014-12-01 17:04:16 +000050# but are whitespace agnostic.
51#
52# Actual regex patterns can be inserted enclosed in '{{' and '}}' brackets. If
53# curly brackets need to be used inside the body of the regex, they need to be
54# enclosed in round brackets. For example, the pattern '{{foo{2}}}' will parse
55# the invalid regex 'foo{2', but '{{(fo{2})}}' will match 'foo'.
56#
57# Regex patterns can be named and referenced later. A new variable is defined
58# with '[[name:regex]]' and can be referenced with '[[name]]'. Variables are
59# only valid within the scope of the defining group. Within a group they cannot
60# be redefined or used undefined.
61#
62# Example:
63# The following assertions can be placed in a Java source file:
64#
65# // CHECK-START: int MyClass.MyMethod() constant_folding (after)
66# // CHECK: [[ID:i[0-9]+]] IntConstant {{11|22}}
67# // CHECK: Return [ [[ID]] ]
68#
69# The engine will attempt to match the check lines against the output of the
70# group named on the first line. Together they verify that the CFG after
71# constant folding returns an integer constant with value either 11 or 22.
72#
73
74import argparse
75import os
76import re
77import shutil
78import sys
79import tempfile
80from subprocess import check_call
81
82class CommonEqualityMixin:
83 """Mixin for class equality as equality of the fields."""
84 def __eq__(self, other):
85 return (isinstance(other, self.__class__)
86 and self.__dict__ == other.__dict__)
87
88 def __ne__(self, other):
89 return not self.__eq__(other)
90
91 def __repr__(self):
92 return "<%s: %s>" % (type(self).__name__, str(self.__dict__))
93
94
95class CheckElement(CommonEqualityMixin):
96 """Single element of the check line."""
97
98 class Variant(object):
99 """Supported language constructs."""
100 Text, Pattern, VarRef, VarDef = range(4)
101
102 def __init__(self, variant, name, pattern):
103 self.variant = variant
104 self.name = name
105 self.pattern = pattern
106
107 @staticmethod
108 def parseText(text):
109 return CheckElement(CheckElement.Variant.Text, None, re.escape(text))
110
111 @staticmethod
112 def parsePattern(patternElem):
113 return CheckElement(CheckElement.Variant.Pattern, None, patternElem[2:len(patternElem)-2])
114
115 @staticmethod
116 def parseVariable(varElem):
117 colonPos = varElem.find(":")
118 if colonPos == -1:
119 # Variable reference
120 name = varElem[2:len(varElem)-2]
121 return CheckElement(CheckElement.Variant.VarRef, name, None)
122 else:
123 # Variable definition
124 name = varElem[2:colonPos]
125 body = varElem[colonPos+1:len(varElem)-2]
126 return CheckElement(CheckElement.Variant.VarDef, name, body)
127
128
129class CheckLine(CommonEqualityMixin):
130 """Representation of a single assertion in the check file formed of one or
131 more regex elements. Matching against an output line is successful only
132 if all regex elements can be matched in the given order."""
133
David Brazdil9a6f20e2014-12-19 11:17:21 +0000134 class Variant(object):
135 """Supported types of assertions."""
136 InOrder, DAG, Not = range(3)
David Brazdilee690a32014-12-01 17:04:16 +0000137
David Brazdil9a6f20e2014-12-19 11:17:21 +0000138 def __init__(self, content, variant=Variant.InOrder, lineNo=-1):
139 self.content = content.strip()
140 self.variant = variant
David Brazdilee690a32014-12-01 17:04:16 +0000141 self.lineNo = lineNo
David Brazdilee690a32014-12-01 17:04:16 +0000142
David Brazdil9a6f20e2014-12-19 11:17:21 +0000143 self.lineParts = self.__parse(self.content)
David Brazdilee690a32014-12-01 17:04:16 +0000144 if not self.lineParts:
145 raise Exception("Empty check line")
146
147 # Returns True if the given Match object was at the beginning of the line.
148 def __isMatchAtStart(self, match):
149 return (match is not None) and (match.start() == 0)
150
151 # Takes in a list of Match objects and returns the minimal start point among
152 # them. If there aren't any successful matches it returns the length of
153 # the searched string.
154 def __firstMatch(self, matches, string):
155 starts = map(lambda m: len(string) if m is None else m.start(), matches)
156 return min(starts)
157
158 # Returns the regex for finding a regex pattern in the check line.
159 def __getPatternRegex(self):
160 rStartSym = "\{\{"
161 rEndSym = "\}\}"
162 rBody = ".+?"
163 return rStartSym + rBody + rEndSym
164
165 # Returns the regex for finding a variable use in the check line.
166 def __getVariableRegex(self):
167 rStartSym = "\[\["
168 rEndSym = "\]\]"
169 rStartOptional = "("
170 rEndOptional = ")?"
171 rName = "[a-zA-Z][a-zA-Z0-9]*"
172 rSeparator = ":"
173 rBody = ".+?"
174 return rStartSym + rName + rStartOptional + rSeparator + rBody + rEndOptional + rEndSym
175
176 # This method parses the content of a check line stripped of the initial
177 # comment symbol and the CHECK keyword.
178 def __parse(self, line):
179 lineParts = []
180 # Loop as long as there is something to parse.
181 while line:
182 # Search for the nearest occurrence of the special markers.
183 matchWhitespace = re.search("\s+", line)
184 matchPattern = re.search(self.__getPatternRegex(), line)
185 matchVariable = re.search(self.__getVariableRegex(), line)
186
187 # If one of the above was identified at the current position, extract them
188 # from the line, parse them and add to the list of line parts.
189 if self.__isMatchAtStart(matchWhitespace):
190 # We want to be whitespace-agnostic so whenever a check line contains
191 # a whitespace, we add a regex pattern for an arbitrary non-zero number
192 # of whitespaces.
193 line = line[matchWhitespace.end():]
194 lineParts.append(CheckElement.parsePattern("{{\s+}}"))
195 elif self.__isMatchAtStart(matchPattern):
196 pattern = line[0:matchPattern.end()]
197 line = line[matchPattern.end():]
198 lineParts.append(CheckElement.parsePattern(pattern))
199 elif self.__isMatchAtStart(matchVariable):
200 var = line[0:matchVariable.end()]
201 line = line[matchVariable.end():]
David Brazdil9a6f20e2014-12-19 11:17:21 +0000202 elem = CheckElement.parseVariable(var)
203 if self.variant == CheckLine.Variant.Not and elem.variant == CheckElement.Variant.VarDef:
204 raise Exception("CHECK-NOT check lines cannot define variables " +
205 "(line " + str(self.lineNo) + ")")
206 lineParts.append(elem)
David Brazdilee690a32014-12-01 17:04:16 +0000207 else:
208 # If we're not currently looking at a special marker, this is a plain
209 # text match all the way until the first special marker (or the end
210 # of the line).
211 firstMatch = self.__firstMatch([ matchWhitespace, matchPattern, matchVariable ], line)
212 text = line[0:firstMatch]
213 line = line[firstMatch:]
214 lineParts.append(CheckElement.parseText(text))
215 return lineParts
216
217 # Returns the regex pattern to be matched in the output line. Variable
218 # references are substituted with their current values provided in the
219 # 'varState' argument.
220 # An exception is raised if a referenced variable is undefined.
221 def __generatePattern(self, linePart, varState):
222 if linePart.variant == CheckElement.Variant.VarRef:
223 try:
224 return re.escape(varState[linePart.name])
225 except KeyError:
226 raise Exception("Use of undefined variable '" + linePart.name + "' " +
227 "(line " + str(self.lineNo))
228 else:
229 return linePart.pattern
230
231 # Attempts to match the check line against a line from the output file with
232 # the given initial variable values. It returns the new variable state if
233 # successful and None otherwise.
234 def match(self, outputLine, initialVarState):
235 initialSearchFrom = 0
236 initialPattern = self.__generatePattern(self.lineParts[0], initialVarState)
237 while True:
238 # Search for the first element on the regex parts list. This will mark
239 # the point on the line from which we will attempt to match the rest of
240 # the check pattern. If this iteration produces only a partial match,
241 # the next iteration will start searching further in the output.
242 firstMatch = re.search(initialPattern, outputLine[initialSearchFrom:])
243 if firstMatch is None:
244 return None
245 matchStart = initialSearchFrom + firstMatch.start()
246 initialSearchFrom += firstMatch.start() + 1
247
248 # Do the full matching on a shadow copy of the variable state. If the
249 # matching fails half-way, we will not need to revert the state.
250 varState = dict(initialVarState)
251
252 # Now try to parse all of the parts of the check line in the right order.
253 # Variable values are updated on-the-fly, meaning that a variable can
254 # be referenced immediately after its definition.
255 fullyMatched = True
256 for part in self.lineParts:
257 pattern = self.__generatePattern(part, varState)
258 match = re.match(pattern, outputLine[matchStart:])
259 if match is None:
260 fullyMatched = False
261 break
262 matchEnd = matchStart + match.end()
263 if part.variant == CheckElement.Variant.VarDef:
264 if part.name in varState:
265 raise Exception("Redefinition of variable '" + part.name + "'" +
266 " (line " + str(self.lineNo) + ")")
267 varState[part.name] = outputLine[matchStart:matchEnd]
268 matchStart = matchEnd
269
270 # Return the new variable state if all parts were successfully matched.
271 # Otherwise loop and try to find another start point on the same line.
272 if fullyMatched:
273 return varState
274
275
276class CheckGroup(CommonEqualityMixin):
277 """Represents a named collection of check lines which are to be matched
278 against an output group of the same name."""
279
280 def __init__(self, name, lines):
281 if name:
282 self.name = name
283 else:
284 raise Exception("Check group does not have a name")
285 if lines:
286 self.lines = lines
287 else:
288 raise Exception("Check group " + self.name + " does not have a body")
289
290 def __headAndTail(self, list):
291 return list[0], list[1:]
292
David Brazdil9a6f20e2014-12-19 11:17:21 +0000293 # Splits a list of check lines at index 'i' such that lines[i] is the first
294 # element whose variant is not equal to the given parameter.
295 def __splitByVariant(self, lines, variant):
296 i = 0
297 while i < len(lines) and lines[i].variant == variant:
298 i += 1
299 return lines[:i], lines[i:]
David Brazdilee690a32014-12-01 17:04:16 +0000300
David Brazdil9a6f20e2014-12-19 11:17:21 +0000301 # Extracts the first sequence of check lines which are independent of each
302 # other's match location, i.e. either consecutive DAG lines or a single
303 # InOrder line. Any Not lines preceeding this sequence are also extracted.
304 def __nextIndependentChecks(self, checkLines):
305 notChecks, checkLines = self.__splitByVariant(checkLines, CheckLine.Variant.Not)
306 if not checkLines:
307 return notChecks, [], []
308
309 head, tail = self.__headAndTail(checkLines)
310 if head.variant == CheckLine.Variant.InOrder:
311 return notChecks, [head], tail
312 else:
313 assert head.variant == CheckLine.Variant.DAG
314 independentChecks, checkLines = self.__splitByVariant(checkLines, CheckLine.Variant.DAG)
315 return notChecks, independentChecks, checkLines
316
317 # If successful, returns the line number of the first output line matching the
318 # check line and the updated variable state. Otherwise returns -1 and None,
319 # respectively. The 'lineFilter' parameter can be used to supply a list of
320 # line numbers (counting from 1) which should be skipped.
321 def __findFirstMatch(self, checkLine, outputLines, lineFilter, varState):
322 matchLineNo = 0
323 for outputLine in outputLines:
324 matchLineNo += 1
325 if matchLineNo in lineFilter:
326 continue
327 newVarState = checkLine.match(outputLine, varState)
328 if newVarState is not None:
329 return matchLineNo, newVarState
330 return -1, None
331
332 # Matches the given positive check lines against the output in order of
333 # appearance. Variable state is propagated but the scope of the search remains
334 # the same for all checks. Each output line can only be matched once.
335 # If all check lines are matched, the resulting variable state is returned
336 # together with the remaining output. The function also returns output lines
337 # which appear before either of the matched lines so they can be tested
338 # against Not checks.
339 def __matchIndependentChecks(self, checkLines, outputLines, varState):
340 # If no checks are provided, skip over the entire output.
341 if not checkLines:
342 return outputLines, varState, []
343
344 # Keep track of which lines have been matched.
345 matchedLines = []
346
347 # Find first unused output line which matches each check line.
348 for checkLine in checkLines:
349 matchLineNo, varState = self.__findFirstMatch(checkLine, outputLines, matchedLines, varState)
350 if varState is None:
351 raise Exception("Could not match line " + str(checkLine))
352 matchedLines.append(matchLineNo)
353
354 # Return new variable state and the output lines which lie outside the
355 # match locations of this independent group.
356 preceedingLines = outputLines[:min(matchedLines)-1]
357 remainingLines = outputLines[max(matchedLines):]
358 return preceedingLines, remainingLines, varState
359
360 # Makes sure that the given check lines do not match any of the given output
361 # lines. Variable state does not change.
362 def __matchNotLines(self, checkLines, outputLines, varState):
363 for checkLine in checkLines:
364 assert checkLine.variant == CheckLine.Variant.Not
365 matchLineNo, varState = self.__findFirstMatch(checkLine, outputLines, [], varState)
366 if varState is not None:
367 raise Exception("CHECK-NOT line " + str(checkLine) + " matches output")
368
369 # Matches the check lines in this group against an output group. It is
370 # responsible for running the checks in the right order and scope, and
371 # for propagating the variable state between the check lines.
372 def match(self, outputGroup):
373 varState = {}
David Brazdilee690a32014-12-01 17:04:16 +0000374 checkLines = self.lines
375 outputLines = outputGroup.body
David Brazdilee690a32014-12-01 17:04:16 +0000376
David Brazdilee690a32014-12-01 17:04:16 +0000377 while checkLines:
David Brazdil9a6f20e2014-12-19 11:17:21 +0000378 # Extract the next sequence of location-independent checks to be matched.
379 notChecks, independentChecks, checkLines = self.__nextIndependentChecks(checkLines)
380 # Match the independent checks.
381 notOutput, outputLines, newVarState = \
382 self.__matchIndependentChecks(independentChecks, outputLines, varState)
383 # Run the Not checks against the output lines which lie between the last
384 # two independent groups or the bounds of the output.
385 self.__matchNotLines(notChecks, notOutput, varState)
386 # Update variable state.
387 varState = newVarState
David Brazdilee690a32014-12-01 17:04:16 +0000388
389class OutputGroup(CommonEqualityMixin):
390 """Represents a named part of the test output against which a check group of
391 the same name is to be matched."""
392
393 def __init__(self, name, body):
394 if name:
395 self.name = name
396 else:
397 raise Exception("Output group does not have a name")
398 if body:
399 self.body = body
400 else:
401 raise Exception("Output group " + self.name + " does not have a body")
402
403
404class FileSplitMixin(object):
405 """Mixin for representing text files which need to be split into smaller
406 chunks before being parsed."""
407
408 def _parseStream(self, stream):
409 lineNo = 0
410 allGroups = []
411 currentGroup = None
412
413 for line in stream:
414 lineNo += 1
415 line = line.strip()
416 if not line:
417 continue
418
419 # Let the child class process the line and return information about it.
420 # The _processLine method can modify the content of the line (or delete it
421 # entirely) and specify whether it starts a new group.
422 processedLine, newGroupName = self._processLine(line, lineNo)
423 if newGroupName is not None:
424 currentGroup = (newGroupName, [])
425 allGroups.append(currentGroup)
426 if processedLine is not None:
427 currentGroup[1].append(processedLine)
428
429 # Finally, take the generated line groups and let the child class process
430 # each one before storing the final outcome.
431 return list(map(lambda group: self._processGroup(group[0], group[1]), allGroups))
432
433
434class CheckFile(FileSplitMixin):
435 """Collection of check groups extracted from the input test file."""
436
437 def __init__(self, prefix, checkStream):
438 self.prefix = prefix
439 self.groups = self._parseStream(checkStream)
440
441 # Attempts to parse a check line. The regex searches for a comment symbol
442 # followed by the CHECK keyword, given attribute and a colon at the very
443 # beginning of the line. Whitespaces are ignored.
444 def _extractLine(self, prefix, line):
445 ignoreWhitespace = "\s*"
446 commentSymbols = ["//", "#"]
447 prefixRegex = ignoreWhitespace + \
448 "(" + "|".join(commentSymbols) + ")" + \
449 ignoreWhitespace + \
450 prefix + ":"
451
452 # The 'match' function succeeds only if the pattern is matched at the
453 # beginning of the line.
454 match = re.match(prefixRegex, line)
455 if match is not None:
456 return line[match.end():].strip()
457 else:
458 return None
459
460 def _processLine(self, line, lineNo):
David Brazdil9a6f20e2014-12-19 11:17:21 +0000461 # Lines beginning with 'CHECK-START' start a new check group.
David Brazdilee690a32014-12-01 17:04:16 +0000462 startLine = self._extractLine(self.prefix + "-START", line)
463 if startLine is not None:
David Brazdil9a6f20e2014-12-19 11:17:21 +0000464 return None, startLine
465
466 # Lines starting only with 'CHECK' are matched in order.
467 plainLine = self._extractLine(self.prefix, line)
468 if plainLine is not None:
469 return (plainLine, CheckLine.Variant.InOrder), None
470
471 # 'CHECK-DAG' lines are no-order assertions.
472 dagLine = self._extractLine(self.prefix + "-DAG", line)
473 if dagLine is not None:
474 return (dagLine, CheckLine.Variant.DAG), None
475
476 # 'CHECK-NOT' lines are no-order negative assertions.
477 notLine = self._extractLine(self.prefix + "-NOT", line)
478 if notLine is not None:
479 return (notLine, CheckLine.Variant.Not), None
480
481 # Other lines are ignored.
482 return None, None
David Brazdilee690a32014-12-01 17:04:16 +0000483
484 def _exceptionLineOutsideGroup(self, line, lineNo):
485 raise Exception("Check file line lies outside a group (line " + str(lineNo) + ")")
486
487 def _processGroup(self, name, lines):
David Brazdil9a6f20e2014-12-19 11:17:21 +0000488 checkLines = list(map(lambda line: CheckLine(line[0], line[1]), lines))
489 return CheckGroup(name, checkLines)
David Brazdilee690a32014-12-01 17:04:16 +0000490
491 def match(self, outputFile, printInfo=False):
492 for checkGroup in self.groups:
493 # TODO: Currently does not handle multiple occurrences of the same group
494 # name, e.g. when a pass is run multiple times. It will always try to
495 # match a check group against the first output group of the same name.
496 outputGroup = outputFile.findGroup(checkGroup.name)
497 if outputGroup is None:
498 raise Exception("Group " + checkGroup.name + " not found in the output")
499 if printInfo:
500 print("TEST " + checkGroup.name + "... ", end="", flush=True)
501 try:
502 checkGroup.match(outputGroup)
503 if printInfo:
504 print("PASSED")
505 except Exception as e:
506 if printInfo:
507 print("FAILED!")
508 raise e
509
510
511class OutputFile(FileSplitMixin):
512 """Representation of the output generated by the test and split into groups
513 within which the checks are performed.
514
515 C1visualizer format is parsed with a state machine which differentiates
516 between the 'compilation' and 'cfg' blocks. The former marks the beginning
517 of a method. It is parsed for the method's name but otherwise ignored. Each
518 subsequent CFG block represents one stage of the compilation pipeline and
519 is parsed into an output group named "<method name> <pass name>".
520 """
521
522 class ParsingState:
523 OutsideBlock, InsideCompilationBlock, StartingCfgBlock, InsideCfgBlock = range(4)
524
525 def __init__(self, outputStream):
526 # Initialize the state machine
527 self.lastMethodName = None
528 self.state = OutputFile.ParsingState.OutsideBlock
529 self.groups = self._parseStream(outputStream)
530
531 def _processLine(self, line, lineNo):
532 if self.state == OutputFile.ParsingState.StartingCfgBlock:
533 # Previous line started a new 'cfg' block which means that this one must
534 # contain the name of the pass (this is enforced by C1visualizer).
535 if re.match("name\s+\"[^\"]+\"", line):
536 # Extract the pass name, prepend it with the name of the method and
537 # return as the beginning of a new group.
538 self.state = OutputFile.ParsingState.InsideCfgBlock
539 return (None, self.lastMethodName + " " + line.split("\"")[1])
540 else:
541 raise Exception("Expected group name in output file (line " + str(lineNo) + ")")
542
543 elif self.state == OutputFile.ParsingState.InsideCfgBlock:
544 if line == "end_cfg":
545 self.state = OutputFile.ParsingState.OutsideBlock
546 return (None, None)
547 else:
548 return (line, None)
549
550 elif self.state == OutputFile.ParsingState.InsideCompilationBlock:
551 # Search for the method's name. Format: method "<name>"
552 if re.match("method\s+\"[^\"]+\"", line):
553 self.lastMethodName = line.split("\"")[1]
554 elif line == "end_compilation":
555 self.state = OutputFile.ParsingState.OutsideBlock
556 return (None, None)
557
558 else: # self.state == OutputFile.ParsingState.OutsideBlock:
559 if line == "begin_cfg":
560 # The line starts a new group but we'll wait until the next line from
561 # which we can extract the name of the pass.
562 if self.lastMethodName is None:
563 raise Exception("Output contains a pass without a method header" +
564 " (line " + str(lineNo) + ")")
565 self.state = OutputFile.ParsingState.StartingCfgBlock
566 return (None, None)
567 elif line == "begin_compilation":
568 self.state = OutputFile.ParsingState.InsideCompilationBlock
569 return (None, None)
570 else:
571 raise Exception("Output line lies outside a group (line " + str(lineNo) + ")")
572
573 def _processGroup(self, name, lines):
574 return OutputGroup(name, lines)
575
576 def findGroup(self, name):
577 for group in self.groups:
578 if group.name == name:
579 return group
580 return None
581
582
583def ParseArguments():
584 parser = argparse.ArgumentParser()
585 parser.add_argument("test_file", help="the source of the test with checking annotations")
586 parser.add_argument("--check-prefix", dest="check_prefix", default="CHECK", metavar="PREFIX",
587 help="prefix of checks in the test file (default: CHECK)")
588 parser.add_argument("--list-groups", dest="list_groups", action="store_true",
589 help="print a list of all groups found in the test output")
590 parser.add_argument("--dump-group", dest="dump_group", metavar="GROUP",
591 help="print the contents of an output group")
592 return parser.parse_args()
593
594
595class cd:
596 """Helper class which temporarily changes the working directory."""
597
598 def __init__(self, newPath):
599 self.newPath = newPath
600
601 def __enter__(self):
602 self.savedPath = os.getcwd()
603 os.chdir(self.newPath)
604
605 def __exit__(self, etype, value, traceback):
606 os.chdir(self.savedPath)
607
608
609def CompileTest(inputFile, tempFolder):
610 classFolder = tempFolder + "/classes"
611 dexFile = tempFolder + "/test.dex"
612 oatFile = tempFolder + "/test.oat"
613 outputFile = tempFolder + "/art.cfg"
614 os.makedirs(classFolder)
615
616 # Build a DEX from the source file. We pass "--no-optimize" to dx to avoid
617 # interference with its optimizations.
618 check_call(["javac", "-d", classFolder, inputFile])
619 check_call(["dx", "--dex", "--no-optimize", "--output=" + dexFile, classFolder])
620
621 # Run dex2oat and export the HGraph. The output is stored into ${PWD}/art.cfg.
622 with cd(tempFolder):
623 check_call(["dex2oat", "-j1", "--dump-passes", "--compiler-backend=Optimizing",
624 "--android-root=" + os.environ["ANDROID_HOST_OUT"],
625 "--boot-image=" + os.environ["ANDROID_HOST_OUT"] + "/framework/core-optimizing.art",
626 "--runtime-arg", "-Xnorelocate", "--dex-file=" + dexFile, "--oat-file=" + oatFile])
627
628 return outputFile
629
630
631def ListGroups(outputFilename):
632 outputFile = OutputFile(open(outputFilename, "r"))
633 for group in outputFile.groups:
634 print(group.name)
635
636
637def DumpGroup(outputFilename, groupName):
638 outputFile = OutputFile(open(outputFilename, "r"))
639 group = outputFile.findGroup(groupName)
640 if group:
641 print("\n".join(group.body))
642 else:
643 raise Exception("Check group " + groupName + " not found in the output")
644
645
646def RunChecks(checkPrefix, checkFilename, outputFilename):
647 checkFile = CheckFile(checkPrefix, open(checkFilename, "r"))
648 outputFile = OutputFile(open(outputFilename, "r"))
649 checkFile.match(outputFile, True)
650
651
652if __name__ == "__main__":
653 args = ParseArguments()
654 tempFolder = tempfile.mkdtemp()
655
656 try:
657 outputFile = CompileTest(args.test_file, tempFolder)
658 if args.list_groups:
659 ListGroups(outputFile)
660 elif args.dump_group:
661 DumpGroup(outputFile, args.dump_group)
662 else:
663 RunChecks(args.check_prefix, args.test_file, outputFile)
664 finally:
665 shutil.rmtree(tempFolder)