idl_release.py revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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    self.release_to_channel = {}
218    for release, version, channel in release_info:
219      self.version_to_release[version] = release
220      self.release_to_version[release] = version
221      self.release_to_channel[release] = channel
222    self.releases = sorted(self.release_to_version.keys())
223    self.versions = sorted(self.version_to_release.keys())
224
225  def GetVersion(self, release):
226    return self.release_to_version.get(release, None)
227
228  def GetVersions(self):
229    return self.versions
230
231  def GetRelease(self, version):
232    return self.version_to_release.get(version, None)
233
234  def GetReleases(self):
235    return self.releases
236
237  def GetReleaseRange(self):
238    return (self.releases[0], self.releases[-1])
239
240  def GetVersionRange(self):
241    return (self.versions[0], self.version[-1])
242
243  def GetChannel(self, release):
244    return self.release_to_channel.get(release, None)
245
246#
247# Test Code
248#
249def TestReleaseNode():
250  FooXX = IDLRelease(None, None)
251  Foo1X = IDLRelease('M14', None)
252  Foo23 = IDLRelease('M15', 'M16')
253
254  assert FooXX.IsRelease('M13')
255  assert FooXX.IsRelease('M14')
256  assert FooXX.InRange('M13', 'M13A')
257  assert FooXX.InRange('M14','M15')
258
259  assert not Foo1X.IsRelease('M13')
260  assert Foo1X.IsRelease('M14')
261  assert Foo1X.IsRelease('M15')
262
263  assert not Foo1X.InRange('M13', 'M14')
264  assert not Foo1X.InRange('M13A', 'M14')
265  assert Foo1X.InRange('M14', 'M15')
266  assert Foo1X.InRange('M15', 'M16')
267
268  assert not Foo23.InRange('M13', 'M14')
269  assert not Foo23.InRange('M13A', 'M14')
270  assert not Foo23.InRange('M14', 'M15')
271  assert Foo23.InRange('M15', 'M16')
272  assert Foo23.InRange('M14', 'M15A')
273  assert Foo23.InRange('M15B', 'M17')
274  assert not Foo23.InRange('M16', 'M17')
275  print "TestReleaseNode - Passed"
276
277
278def TestReleaseListWarning():
279  FooXX = IDLRelease(None, None)
280  Foo1X = IDLRelease('M14', None)
281  Foo23 = IDLRelease('M15', 'M16')
282  Foo45 = IDLRelease('M17', 'M18')
283
284  # Add nodes out of order should fail
285  ReportClear()
286  releases = IDLReleaseList()
287  assert releases.AddNode(Foo23)
288  assert releases.AddNode(Foo45)
289  assert warning
290  print "TestReleaseListWarning - Passed"
291
292
293def TestReleaseListError():
294  FooXX = IDLRelease(None, None)
295  Foo1X = IDLRelease('M14', None)
296  Foo23 = IDLRelease('M15', 'M16')
297  Foo45 = IDLRelease('M17', 'M18')
298
299  # Add nodes out of order should fail
300  ReportClear()
301  releases = IDLReleaseList()
302  assert releases.AddNode(FooXX)
303  assert releases.AddNode(Foo23)
304  assert not releases.AddNode(Foo1X)
305  assert error
306  print "TestReleaseListError - Passed"
307
308
309def TestReleaseListOK():
310  FooXX = IDLRelease(None, None)
311  Foo1X = IDLRelease('M14', None)
312  Foo23 = IDLRelease('M15', 'M16')
313  Foo45 = IDLRelease('M17', 'M18')
314
315  # Add nodes in order should work
316  ReportClear()
317  releases = IDLReleaseList()
318  assert releases.AddNode(FooXX)
319  assert releases.AddNode(Foo1X)
320  assert releases.AddNode(Foo23)
321  assert not error and not warning
322  assert releases.AddNode(Foo45)
323  assert warning
324
325  assert releases.FindRelease('M13') == FooXX
326  assert releases.FindRelease('M14') == Foo1X
327  assert releases.FindRelease('M15') == Foo23
328  assert releases.FindRelease('M16') == None
329  assert releases.FindRelease('M17') == Foo45
330  assert releases.FindRelease('M18') == None
331
332  assert releases.FindRange('M13','M14') == [FooXX]
333  assert releases.FindRange('M13','M17') == [FooXX, Foo1X, Foo23]
334  assert releases.FindRange('M16','M17') == []
335  assert releases.FindRange(None, None) == [FooXX, Foo1X, Foo23, Foo45]
336
337  # Verify we can find the correct versions
338  print "TestReleaseListOK - Passed"
339
340
341def TestReleaseMap():
342  print "TestReleaseMap- Passed"
343
344
345def Main(args):
346  TestReleaseNode()
347  TestReleaseListWarning()
348  TestReleaseListError()
349  TestReleaseListOK()
350  print "Passed"
351  return 0
352
353
354if __name__ == '__main__':
355  sys.exit(Main(sys.argv[1:]))
356
357