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