idl_namespace.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"""
7IDLNamespace 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_option import GetOption, Option, ParseOptions
16from idl_log import ErrOut, InfoOut, WarnOut
17from idl_release import IDLRelease, IDLReleaseList
18
19Option('label', 'Use the specifed label blocks.', default='Chrome')
20Option('namespace_debug', 'Use the specified release')
21
22
23#
24# IDLNamespace
25#
26# IDLNamespace provides a mapping between a symbol name and an IDLReleaseList
27# which contains IDLRelease objects.  It provides an interface for fetching
28# one or more IDLNodes based on a release or range of releases.
29#
30class IDLNamespace(object):
31  def __init__(self, parent, name):
32    self._name_to_releases = {}
33    self._parent = parent
34    self._name = name
35
36  def Dump(self):
37    for name in self._name_to_releases:
38      InfoOut.Log('NAME=%s' % name)
39      for cver in self._name_to_releases[name].GetReleases():
40        InfoOut.Log('  %s' % cver)
41      InfoOut.Log('')
42
43  def FindRelease(self, name, release):
44    verlist = self._name_to_releases.get(name, None)
45    if verlist == None:
46      if self._parent:
47        return self._parent.FindRelease(name, release)
48      else:
49        return None
50    return verlist.FindRelease(release)
51
52  def FindRange(self, name, rmin, rmax):
53    verlist = self._name_to_releases.get(name, None)
54    if verlist == None:
55      if self._parent:
56        return self._parent.FindRange(name, rmin, rmax)
57      else:
58        return []
59    return verlist.FindRange(rmin, rmax)
60
61  def FindList(self, name):
62    verlist = self._name_to_releases.get(name, None)
63    if verlist == None:
64      if self._parent:
65        return self._parent.FindList(name)
66    return verlist
67
68  def AddNode(self, node):
69    name = node.GetName()
70    verlist = self._name_to_releases.setdefault(name,IDLReleaseList())
71    if GetOption('namespace_debug'):
72        print "Adding to namespace: %s" % node
73    return verlist.AddNode(node)
74
75
76
77#
78# Testing Code
79#
80
81#
82# MockNode
83#
84# Mocks the IDLNode to support error, warning handling, and string functions.
85#
86class MockNode(IDLRelease):
87  def __init__(self, name, rmin, rmax):
88    self.name = name
89    self.rmin = rmin
90    self.rmax = rmax
91    self.errors = []
92    self.warns = []
93    self.properties = {
94        'NAME': name,
95        'release': rmin,
96        'deprecate' : rmax
97        }
98
99  def __str__(self):
100    return '%s (%s : %s)' % (self.name, self.rmin, self.rmax)
101
102  def GetName(self):
103    return self.name
104
105  def Error(self, msg):
106    if GetOption('release_debug'): print 'Error: %s' % msg
107    self.errors.append(msg)
108
109  def Warn(self, msg):
110    if GetOption('release_debug'): print 'Warn: %s' % msg
111    self.warns.append(msg)
112
113  def GetProperty(self, name):
114    return self.properties.get(name, None)
115
116errors = 0
117#
118# DumpFailure
119#
120# Dumps all the information relevant  to an add failure.
121def DumpFailure(namespace, node, msg):
122  global errors
123  print '\n******************************'
124  print 'Failure: %s %s' % (node, msg)
125  for warn in node.warns:
126    print '  WARN: %s' % warn
127  for err in node.errors:
128    print '  ERROR: %s' % err
129  print '\n'
130  namespace.Dump()
131  print '******************************\n'
132  errors += 1
133
134# Add expecting no errors or warnings
135def AddOkay(namespace, node):
136  okay = namespace.AddNode(node)
137  if not okay or node.errors or node.warns:
138    DumpFailure(namespace, node, 'Expected success')
139
140# Add expecting a specific warning
141def AddWarn(namespace, node, msg):
142  okay = namespace.AddNode(node)
143  if not okay or node.errors or not node.warns:
144    DumpFailure(namespace, node, 'Expected warnings')
145  if msg not in node.warns:
146    DumpFailure(namespace, node, 'Expected warning: %s' % msg)
147
148# Add expecting a specific error any any number of warnings
149def AddError(namespace, node, msg):
150  okay = namespace.AddNode(node)
151  if okay or not node.errors:
152    DumpFailure(namespace, node, 'Expected errors')
153  if msg not in node.errors:
154    DumpFailure(namespace, node, 'Expected error: %s' % msg)
155    print ">>%s<<\n>>%s<<\n" % (node.errors[0], msg)
156
157# Verify that a FindRelease call on the namespace returns the expected node.
158def VerifyFindOne(namespace, name, release, node):
159  global errors
160  if (namespace.FindRelease(name, release) != node):
161    print "Failed to find %s as release %f of %s" % (node, release, name)
162    namespace.Dump()
163    print "\n"
164    errors += 1
165
166# Verify that a FindRage call on the namespace returns a set of expected nodes.
167def VerifyFindAll(namespace, name, rmin, rmax, nodes):
168  global errors
169  out = namespace.FindRange(name, rmin, rmax)
170  if (out != nodes):
171    print "Found [%s] instead of[%s] for releases %f to %f of %s" % (
172        ' '.join([str(x) for x in out]),
173        ' '.join([str(x) for x in nodes]),
174        rmin,
175        rmax,
176        name)
177    namespace.Dump()
178    print "\n"
179    errors += 1
180
181def Main(args):
182  global errors
183  ParseOptions(args)
184
185  InfoOut.SetConsole(True)
186
187  namespace = IDLNamespace(None)
188
189  FooXX = MockNode('foo', None, None)
190  Foo1X = MockNode('foo', 1.0, None)
191  Foo2X = MockNode('foo', 2.0, None)
192  Foo3X = MockNode('foo', 3.0, None)
193
194  # Verify we succeed with undeprecated adds
195  AddOkay(namespace, FooXX)
196  AddOkay(namespace, Foo1X)
197  AddOkay(namespace, Foo3X)
198  # Verify we fail to add a node between undeprecated releases
199  AddError(namespace, Foo2X,
200           'Overlap in releases: 3.0 vs 2.0 when adding foo (2.0 : None)')
201
202  BarXX = MockNode('bar', None, None)
203  Bar12 = MockNode('bar', 1.0, 2.0)
204  Bar23 = MockNode('bar', 2.0, 3.0)
205  Bar34 = MockNode('bar', 3.0, 4.0)
206
207
208  # Verify we succeed with fully qualified releases
209  namespace = IDLNamespace(namespace)
210  AddOkay(namespace, BarXX)
211  AddOkay(namespace, Bar12)
212  # Verify we warn when detecting a gap
213  AddWarn(namespace, Bar34, 'Gap in release numbers.')
214  # Verify we fail when inserting into this gap
215  # (NOTE: while this could be legal, it is sloppy so we disallow it)
216  AddError(namespace, Bar23, 'Declarations out of order.')
217
218  # Verify local namespace
219  VerifyFindOne(namespace, 'bar', 0.0, BarXX)
220  VerifyFindAll(namespace, 'bar', 0.5, 1.5, [BarXX, Bar12])
221
222  # Verify the correct release of the object is found recursively
223  VerifyFindOne(namespace, 'foo', 0.0, FooXX)
224  VerifyFindOne(namespace, 'foo', 0.5, FooXX)
225  VerifyFindOne(namespace, 'foo', 1.0, Foo1X)
226  VerifyFindOne(namespace, 'foo', 1.5, Foo1X)
227  VerifyFindOne(namespace, 'foo', 3.0, Foo3X)
228  VerifyFindOne(namespace, 'foo', 100.0, Foo3X)
229
230  # Verify the correct range of objects is found
231  VerifyFindAll(namespace, 'foo', 0.0, 1.0, [FooXX])
232  VerifyFindAll(namespace, 'foo', 0.5, 1.0, [FooXX])
233  VerifyFindAll(namespace, 'foo', 1.0, 1.1, [Foo1X])
234  VerifyFindAll(namespace, 'foo', 0.5, 1.5, [FooXX, Foo1X])
235  VerifyFindAll(namespace, 'foo', 0.0, 3.0, [FooXX, Foo1X])
236  VerifyFindAll(namespace, 'foo', 3.0, 100.0, [Foo3X])
237
238  FooBar = MockNode('foobar', 1.0, 2.0)
239  namespace = IDLNamespace(namespace)
240  AddOkay(namespace, FooBar)
241
242  if errors:
243    print 'Test failed with %d errors.' % errors
244  else:
245    print 'Passed.'
246  return errors
247
248
249if __name__ == '__main__':
250  sys.exit(Main(sys.argv[1:]))
251
252