3 from __future__
import absolute_import
, division
, print_function
12 class DeltaAlgorithm(object):
16 def test(self
, changes
):
21 def getTestResult(self
, changes
):
22 # There is no reason to cache successful tests because we will
23 # always reduce the changeset when we see one.
25 changeset
= frozenset(changes
)
26 if changeset
in self
.cache
:
28 elif not self
.test(changes
):
29 self
.cache
.add(changeset
)
34 def run(self
, changes
, force
=False):
35 # Make sure the initial test passes, if not then (a) either
36 # the user doesn't expect monotonicity, and we may end up
37 # doing O(N^2) tests, or (b) the test is wrong. Avoid the
38 # O(N^2) case unless user requests it.
40 if not self
.getTestResult(changes
):
41 raise ValueError('Initial test passed to delta fails.')
43 # Check empty set first to quickly find poor test functions.
44 if self
.getTestResult(set()):
47 return self
.delta(changes
, self
.split(changes
))
50 """split(set) -> [sets]
52 Partition a set into one or two pieces.
55 # There are many ways to split, we could do a better job with more
56 # context information (but then the API becomes grosser).
62 return L
[:mid
],L
[mid
:]
64 def delta(self
, c
, sets
):
65 # assert(reduce(set.union, sets, set()) == c)
67 # If there is nothing left we can remove, we are done.
71 # Look for a passing subset.
72 res
= self
.search(c
, sets
)
76 # Otherwise, partition sets if possible; if not we are done.
77 refined
= sum(map(list, map(self
.split
, sets
)), [])
78 if len(refined
) == len(sets
):
81 return self
.delta(c
, refined
)
83 def search(self
, c
, sets
):
84 for i
,S
in enumerate(sets
):
85 # If test passes on this subset alone, recurse.
86 if self
.getTestResult(S
):
87 return self
.delta(S
, self
.split(S
))
89 # Otherwise if we have more than two sets, see if test
90 # pases without this subset.
92 complement
= sum(sets
[:i
] + sets
[i
+1:],[])
93 if self
.getTestResult(complement
):
94 return self
.delta(complement
, sets
[:i
] + sets
[i
+1:])
99 def __init__(self
, type, data
, flags
, file, line
, column
):
107 kTokenRE
= re
.compile(r
"""([a-z_]+) '(.*)'\t(.*)\tLoc=<(.*):(.*):(.*)>""",
108 re
.DOTALL | re
.MULTILINE
)
111 p
= subprocess
.Popen(['clang','-dump-raw-tokens',path
],
112 stdin
=subprocess
.PIPE
,
113 stdout
=subprocess
.PIPE
,
114 stderr
=subprocess
.PIPE
)
115 out
,err
= p
.communicate()
119 for ln
in err
.split('\n'):
120 # Silly programmers refuse to print in simple machine readable
125 collect
= collect
+ '\n' + ln
126 if 'Loc=<' in ln
and ln
.endswith('>'):
127 ln
,collect
= collect
,None
128 tokens
.append(Token(*kTokenRE
.match(ln
).groups()))
134 class TMBDDelta(DeltaAlgorithm
):
135 def __init__(self
, testProgram
, tokenLists
, log
):
136 def patchName(name
, suffix
):
137 base
,ext
= os
.path
.splitext(name
)
138 return base
+ '.' + suffix
+ ext
139 super(TMBDDelta
, self
).__init
__()
140 self
.testProgram
= testProgram
141 self
.tokenLists
= tokenLists
142 self
.tempFiles
= [patchName(f
,'tmp')
143 for f
,_
in self
.tokenLists
]
144 self
.targetFiles
= [patchName(f
,'ok')
145 for f
,_
in self
.tokenLists
]
149 def writeFiles(self
, changes
, fileNames
):
150 assert len(fileNames
) == len(self
.tokenLists
)
151 byFile
= [[] for i
in self
.tokenLists
]
155 for i
,(file,tokens
) in enumerate(self
.tokenLists
):
156 f
= open(fileNames
[i
],'w')
163 def test(self
, changes
):
166 byFile
= self
.writeFiles(changes
, self
.tempFiles
)
169 print('TEST - ', end
=' ', file=sys
.stderr
)
171 for i
,(file,_
) in enumerate(self
.tokenLists
):
174 sys
.stderr
.write('\n ')
175 sys
.stderr
.write('%s:%d tokens: [' % (file,len(byFile
[i
])))
178 if prev
is None or j
!= prev
+ 1:
180 sys
.stderr
.write('%d][' % prev
)
181 sys
.stderr
.write(str(j
))
182 sys
.stderr
.write(':')
185 sys
.stderr
.write(str(byFile
[i
][-1]))
186 sys
.stderr
.write('] ')
188 print(', '.join(['%s:%d tokens' % (file, len(byFile
[i
]))
189 for i
,(file,_
) in enumerate(self
.tokenLists
)]), end
=' ', file=sys
.stderr
)
191 p
= subprocess
.Popen([self
.testProgram
] + self
.tempFiles
)
195 self
.writeFiles(changes
, self
.targetFiles
)
198 print('=> %s' % res
, file=sys
.stderr
)
201 print('\nSUCCESS (%d tokens)' % len(changes
))
203 sys
.stderr
.write('.')
208 res
= super(TMBDDelta
, self
).run([(i
,j
)
209 for i
,(file,tokens
) in enumerate(self
.tokenLists
)
210 for j
in range(len(tokens
))])
211 self
.writeFiles(res
, self
.targetFiles
)
213 print(file=sys
.stderr
)
216 def tokenBasedMultiDelta(program
, files
, log
):
217 # Read in the lists of tokens.
218 tokenLists
= [(file, [t
.data
for t
in getTokens(file)])
221 numTokens
= sum([len(tokens
) for _
,tokens
in tokenLists
])
222 print("Delta on %s with %d tokens." % (', '.join(files
), numTokens
))
224 tbmd
= TMBDDelta(program
, tokenLists
, log
)
228 print("Finished %s with %d tokens (in %d tests)." % (', '.join(tbmd
.targetFiles
),
233 from optparse
import OptionParser
, OptionGroup
234 parser
= OptionParser("%prog <test program> {files+}")
235 parser
.add_option("", "--debug", dest
="debugLevel",
236 help="set debug level [default %default]",
237 action
="store", type=int, default
=0)
238 (opts
, args
) = parser
.parse_args()
241 parser
.error('Invalid number of arguments.')
243 program
,files
= args
[0],args
[1:]
245 md
= tokenBasedMultiDelta(program
, files
, log
=opts
.debugLevel
)
247 if __name__
== '__main__':
250 except KeyboardInterrupt:
251 print('Interrupted.', file=sys
.stderr
)
252 os
._exit
(1) # Avoid freeing our giant cache.