idl_release.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7IDLRelease for PPAPI
8
9This file defines the behavior of the AST namespace which allows for resolving
10a symbol as one or more AST nodes given a Release or range of Releases.
11"""
12
13import sys
14
15from idl_log import ErrOut, InfoOut, WarnOut
16from idl_option import GetOption, Option, ParseOptions
17
18Option('release_debug', 'Debug Release data')
19Option('wgap', 'Ignore Release gap warning')
20
21
22#
23# Module level functions and data used for testing.
24#
25error = None
26warning = None
27def ReportReleaseError(msg):
28  global error
29  error = msg
30
31def ReportReleaseWarning(msg):
32  global warning
33  warning = msg
34
35def ReportClear():
36  global error, warning
37  error = None
38  warning = None
39
40#
41# IDLRelease
42#
43# IDLRelease is an object which stores the association of a given symbol
44# name, with an AST node for a range of Releases for that object.
45#
46# A vmin value of None indicates that the object begins at the earliest
47# available Release number.  The value of vmin is always inclusive.
48
49# A vmax value of None indicates that the object is never deprecated, so
50# it exists until it is overloaded or until the latest available Release.
51# The value of vmax is always exclusive, representing the first Release
52# on which the object is no longer valid.
53class IDLRelease(object):
54  def __init__(self, rmin, rmax):
55    self.rmin = rmin
56    self.rmax = rmax
57
58  def __str__(self):
59    if not self.rmin:
60      rmin = '0'
61    else:
62      rmin = str(self.rmin)
63    if not self.rmax:
64      rmax = '+oo'
65    else:
66      rmax = str(self.rmax)
67    return '[%s,%s)' % (rmin, rmax)
68
69  def SetReleaseRange(self, rmin, rmax):
70    self.rmin = rmin
71    self.rmax = rmax
72
73  # True, if Release falls within the interval [self.vmin, self.vmax)
74  def IsRelease(self, release):
75    if self.rmax and self.rmax <= release:
76      return False
77    if self.rmin and self.rmin > release:
78      return False
79    if GetOption('release_debug'):
80      InfoOut.Log('%f is in %s' % (release, self))
81    return True
82
83  # True, if Release falls within the interval [self.vmin, self.vmax)
84  def InReleases(self, releases):
85    if not releases: return False
86
87    # Check last release first, since InRange does not match last item
88    if self.IsRelease(releases[-1]): return True
89    if len(releases) > 1:
90      return self.InRange(releases[0], releases[-1])
91    return False
92
93  # True, if interval [vmin, vmax) overlaps interval [self.vmin, self.vmax)
94  def InRange(self, rmin, rmax):
95    assert (rmin == None) or rmin < rmax
96
97    # An min of None always passes a min bound test
98    # An max of None always passes a max bound test
99    if rmin is not None and self.rmax is not None:
100      if self.rmax <= rmin:
101        return False
102    if rmax is not None and self.rmin is not None:
103      if self.rmin >= rmax:
104        return False
105
106    if GetOption('release_debug'):
107      InfoOut.Log('%f to %f is in %s' % (rmin, rmax, self))
108    return True
109
110  def GetMinMax(self, releases = None):
111    if not releases:
112      return self.rmin, self.rmax
113
114    if not self.rmin:
115      rmin = releases[0]
116    else:
117      rmin = str(self.rmin)
118    if not self.rmax:
119      rmax = releases[-1]
120    else:
121      rmax = str(self.rmax)
122    return (rmin, rmax)
123
124  def SetMin(self, release):
125    assert not self.rmin
126    self.rmin = release
127
128  def Error(self, msg):
129    ReportReleaseError(msg)
130
131  def Warn(self, msg):
132    ReportReleaseWarning(msg)
133
134
135#
136# IDLReleaseList
137#
138# IDLReleaseList is a list based container for holding IDLRelease
139# objects in order.  The IDLReleaseList can be added to, and searched by
140# range.  Objects are stored in order, and must be added in order.
141#
142class IDLReleaseList(object):
143  def __init__(self):
144    self._nodes = []
145
146  def GetReleases(self):
147    return self._nodes
148
149  def FindRelease(self, release):
150    for node in self._nodes:
151      if node.IsRelease(release):
152        return node
153    return None
154
155  def FindRange(self, rmin, rmax):
156    assert (rmin == None) or rmin != rmax
157
158    out = []
159    for node in self._nodes:
160      if node.InRange(rmin, rmax):
161        out.append(node)
162    return out
163
164  def AddNode(self, node):
165    if GetOption('release_debug'):
166      InfoOut.Log('\nAdding %s %s' % (node.Location(), node))
167    last = None
168
169    # Check current releases in that namespace
170    for cver in self._nodes:
171      if GetOption('release_debug'): InfoOut.Log('  Checking %s' % cver)
172
173      # We should only be missing a 'release' tag for the first item.
174      if not node.rmin:
175        node.Error('Missing release on overload of previous %s.' %
176                   cver.Location())
177        return False
178
179      # If the node has no max, then set it to this one
180      if not cver.rmax:
181        cver.rmax = node.rmin
182        if GetOption('release_debug'): InfoOut.Log('  Update %s' % cver)
183
184      # if the max and min overlap, than's an error
185      if cver.rmax > node.rmin:
186        if node.rmax and cver.rmin >= node.rmax:
187          node.Error('Declarations out of order.')
188        else:
189          node.Error('Overlap in releases: %s vs %s when adding %s' %
190                     (cver.rmax, node.rmin, node))
191        return False
192      last = cver
193
194    # Otherwise, the previous max and current min should match
195    # unless this is the unlikely case of something being only
196    # temporarily deprecated.
197    if last and last.rmax != node.rmin:
198      node.Warn('Gap in release numbers.')
199
200    # If we made it here, this new node must be the 'newest'
201    # and does not overlap with anything previously added, so
202    # we can add it to the end of the list.
203    if GetOption('release_debug'): InfoOut.Log('Done %s' % node)
204    self._nodes.append(node)
205    return True
206
207#
208# IDLReleaseMap
209#
210# A release map, can map from an float interface release, to a global
211# release string.
212#
213class IDLReleaseMap(object):
214  def __init__(self, release_info):
215    self.version_to_release = {}
216    self.release_to_version = {}
217    for release, version in release_info:
218      self.version_to_release[version] = release
219      self.release_to_version[release] = version
220    self.releases = sorted(self.release_to_version.keys())
221    self.versions = sorted(self.version_to_release.keys())
222
223  def GetVersion(self, release):
224    return self.release_to_version.get(release, None)
225
226  def GetVersions(self):
227    return self.versions
228
229  def GetRelease(self, version):
230    return self.version_to_release.get(version, None)
231
232  def GetReleases(self):
233    return self.releases
234
235  def GetReleaseRange(self):
236    return (self.releases[0], self.releases[-1])
237
238  def GetVersionRange(self):
239    return (self.versions[0], self.version[-1])
240
241#
242# Test Code
243#
244def TestReleaseNode():
245  FooXX = IDLRelease(None, None)
246  Foo1X = IDLRelease('M14', None)
247  Foo23 = IDLRelease('M15', 'M16')
248
249  assert FooXX.IsRelease('M13')
250  assert FooXX.IsRelease('M14')
251  assert FooXX.InRange('M13', 'M13A')
252  assert FooXX.InRange('M14','M15')
253
254  assert not Foo1X.IsRelease('M13')
255  assert Foo1X.IsRelease('M14')
256  assert Foo1X.IsRelease('M15')
257
258  assert not Foo1X.InRange('M13', 'M14')
259  assert not Foo1X.InRange('M13A', 'M14')
260  assert Foo1X.InRange('M14', 'M15')
261  assert Foo1X.InRange('M15', 'M16')
262
263  assert not Foo23.InRange('M13', 'M14')
264  assert not Foo23.InRange('M13A', 'M14')
265  assert not Foo23.InRange('M14', 'M15')
266  assert Foo23.InRange('M15', 'M16')
267  assert Foo23.InRange('M14', 'M15A')
268  assert Foo23.InRange('M15B', 'M17')
269  assert not Foo23.InRange('M16', 'M17')
270  print "TestReleaseNode - Passed"
271
272
273def TestReleaseListWarning():
274  FooXX = IDLRelease(None, None)
275  Foo1X = IDLRelease('M14', None)
276  Foo23 = IDLRelease('M15', 'M16')
277  Foo45 = IDLRelease('M17', 'M18')
278
279  # Add nodes out of order should fail
280  ReportClear()
281  releases = IDLReleaseList()
282  assert releases.AddNode(Foo23)
283  assert releases.AddNode(Foo45)
284  assert warning
285  print "TestReleaseListWarning - Passed"
286
287
288def TestReleaseListError():
289  FooXX = IDLRelease(None, None)
290  Foo1X = IDLRelease('M14', None)
291  Foo23 = IDLRelease('M15', 'M16')
292  Foo45 = IDLRelease('M17', 'M18')
293
294  # Add nodes out of order should fail
295  ReportClear()
296  releases = IDLReleaseList()
297  assert releases.AddNode(FooXX)
298  assert releases.AddNode(Foo23)
299  assert not releases.AddNode(Foo1X)
300  assert error
301  print "TestReleaseListError - Passed"
302
303
304def TestReleaseListOK():
305  FooXX = IDLRelease(None, None)
306  Foo1X = IDLRelease('M14', None)
307  Foo23 = IDLRelease('M15', 'M16')
308  Foo45 = IDLRelease('M17', 'M18')
309
310  # Add nodes in order should work
311  ReportClear()
312  releases = IDLReleaseList()
313  assert releases.AddNode(FooXX)
314  assert releases.AddNode(Foo1X)
315  assert releases.AddNode(Foo23)
316  assert not error and not warning
317  assert releases.AddNode(Foo45)
318  assert warning
319
320  assert releases.FindRelease('M13') == FooXX
321  assert releases.FindRelease('M14') == Foo1X
322  assert releases.FindRelease('M15') == Foo23
323  assert releases.FindRelease('M16') == None
324  assert releases.FindRelease('M17') == Foo45
325  assert releases.FindRelease('M18') == None
326
327  assert releases.FindRange('M13','M14') == [FooXX]
328  assert releases.FindRange('M13','M17') == [FooXX, Foo1X, Foo23]
329  assert releases.FindRange('M16','M17') == []
330  assert releases.FindRange(None, None) == [FooXX, Foo1X, Foo23, Foo45]
331
332  # Verify we can find the correct versions
333  print "TestReleaseListOK - Passed"
334
335
336def TestReleaseMap():
337  print "TestReleaseMap- Passed"
338
339
340def Main(args):
341  TestReleaseNode()
342  TestReleaseListWarning()
343  TestReleaseListError()
344  TestReleaseListOK()
345  print "Passed"
346  return 0
347
348
349if __name__ == '__main__':
350  sys.exit(Main(sys.argv[1:]))
351
352