summaryrefslogtreecommitdiffstats
path: root/tools/generate_stubs/generate_stubs_unittest.py
blob: 85a74c086b7e7da9bb3f308b075563a2521d96da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Unittest for the generate_stubs.py.

Since generate_stubs.py is a code generator, it is hard to do a very good
test.  Instead of creating a golden-file test, which might be flakey, this
test elects instead to verify that various components "exist" within the
generated file as a sanity check.  In particular, there is a simple hit
test to make sure that umbrella functions, etc., do try and include every
function they are responsible for invoking.  Missing an invocation is quite
easily missed.

There is no attempt to verify ordering of different components, or whether
or not those components are going to parse incorrectly because of prior
errors or positioning.  Most of that should be caught really fast anyways
during any attempt to use a badly behaving script.
"""

import generate_stubs as gs
import re
import StringIO
import sys
import unittest


def _MakeSignature(return_type, name, params):
  return {'return_type': return_type,
          'name': name,
          'params': params}


SIMPLE_SIGNATURES = [
    ('int foo(int a)', _MakeSignature('int', 'foo', ['int a'])),
    ('int bar(int a, double b)', _MakeSignature('int', 'bar',
                                                ['int a', 'double b'])),
    ('int baz(void)', _MakeSignature('int', 'baz', ['void'])),
    ('void quux(void)', _MakeSignature('void', 'quux', ['void'])),
    ('void waldo(void);', _MakeSignature('void', 'waldo', ['void'])),
    ('int corge(void);', _MakeSignature('int', 'corge', ['void'])),
    ]

TRICKY_SIGNATURES = [
    ('const struct name *foo(int a, struct Test* b);  ',
     _MakeSignature('const struct name *',
                    'foo',
                    ['int a', 'struct Test* b'])),
    ('const struct name &foo(int a, struct Test* b);',
     _MakeSignature('const struct name &',
                    'foo',
                    ['int a', 'struct Test* b'])),
    ('const struct name &_foo(int a, struct Test* b);',
     _MakeSignature('const struct name &',
                    '_foo',
                    ['int a', 'struct Test* b'])),
    ('struct name const * const _foo(int a, struct Test* b) '
     '__attribute__((inline));',
     _MakeSignature('struct name const * const',
                    '_foo',
                    ['int a', 'struct Test* b']))
    ]

INVALID_SIGNATURES = ['I am bad', 'Seriously bad(', ';;;']


class GenerateStubModuleFunctionsUnittest(unittest.TestCase):
  def testExtractModuleName(self):
    self.assertEqual('somefile-2', gs.ExtractModuleName('somefile-2.ext'))

  def testParseSignatures_EmptyFile(self):
    # Empty file just generates empty signatures.
    infile = StringIO.StringIO()
    signatures = gs.ParseSignatures(infile)
    self.assertEqual(0, len(signatures))

  def testParseSignatures_SimpleSignatures(self):
    file_contents = '\n'.join([x[0] for x in SIMPLE_SIGNATURES])
    infile = StringIO.StringIO(file_contents)
    signatures = gs.ParseSignatures(infile)
    self.assertEqual(len(SIMPLE_SIGNATURES), len(signatures))

    # We assume signatures are in order.
    for i in xrange(len(SIMPLE_SIGNATURES)):
      self.assertEqual(SIMPLE_SIGNATURES[i][1], signatures[i],
                       msg='Expected %s\nActual %s\nFor %s' %
                       (SIMPLE_SIGNATURES[i][1],
                        signatures[i],
                        SIMPLE_SIGNATURES[i][0]))

  def testParseSignatures_TrickySignatures(self):
    file_contents = '\n'.join([x[0] for x in TRICKY_SIGNATURES])
    infile = StringIO.StringIO(file_contents)
    signatures = gs.ParseSignatures(infile)
    self.assertEqual(len(TRICKY_SIGNATURES), len(signatures))

    # We assume signatures are in order.
    for i in xrange(len(TRICKY_SIGNATURES)):
      self.assertEqual(TRICKY_SIGNATURES[i][1], signatures[i],
                       msg='Expected %s\nActual %s\nFor %s' %
                       (TRICKY_SIGNATURES[i][1],
                        signatures[i],
                        TRICKY_SIGNATURES[i][0]))

  def testParseSignatures_InvalidSignatures(self):
    for i in INVALID_SIGNATURES:
      infile = StringIO.StringIO(i)
      self.assertRaises(gs.BadSignatureError, gs.ParseSignatures, infile)

  def testParseSignatures_CommentsIgnored(self):
    my_sigs = []
    my_sigs.append('# a comment')
    my_sigs.append(SIMPLE_SIGNATURES[0][0])
    my_sigs.append('# another comment')
    my_sigs.append(SIMPLE_SIGNATURES[0][0])
    my_sigs.append('# a third comment')
    my_sigs.append(SIMPLE_SIGNATURES[0][0])
    my_sigs.append('// a fourth comment')
    my_sigs.append(SIMPLE_SIGNATURES[0][0])
    my_sigs.append('//')
    my_sigs.append(SIMPLE_SIGNATURES[0][0])

    file_contents = '\n'.join(my_sigs)
    infile = StringIO.StringIO(file_contents)
    signatures = gs.ParseSignatures(infile)
    self.assertEqual(5, len(signatures))


class WindowsLibUnittest(unittest.TestCase):
  def testWriteWindowsDefFile(self):
    module_name = 'my_module-1'
    signatures = [sig[1] for sig in SIMPLE_SIGNATURES]
    outfile = StringIO.StringIO()
    gs.WriteWindowsDefFile(module_name, signatures, outfile)
    contents = outfile.getvalue()

    # Check that the file header is correct.
    self.assertTrue(contents.startswith("""LIBRARY %s
EXPORTS
""" % module_name))

    # Check that the signatures were exported.
    for sig in signatures:
      pattern = '\n  %s\n' % sig['name']
      self.assertTrue(re.search(pattern, contents),
                      msg='Expected match of "%s" in %s' % (pattern, contents))

  def testQuietRun(self):
    output = StringIO.StringIO()
    gs.QuietRun([sys.executable,
                 '-c', 'print "line 1 and suffix\\nline 2"'],
                write_to=output)
    self.assertEqual('line 1 and suffix\nline 2\n', output.getvalue())

    output = StringIO.StringIO()
    gs.QuietRun([sys.executable,
                 '-c', 'print "line 1 and suffix\\nline 2"'],
                 filter='line 1', write_to=output)
    self.assertEqual('line 2\n', output.getvalue())


class PosixStubWriterUnittest(unittest.TestCase):
  def setUp(self):
    self.module_name = 'my_module-1'
    self.signatures = [sig[1] for sig in SIMPLE_SIGNATURES]
    self.out_dir = 'out_dir'
    self.writer = gs.PosixStubWriter(self.module_name, '', self.signatures)

  def testEnumName(self):
    self.assertEqual('kModuleMy_module1',
                     gs.PosixStubWriter.EnumName(self.module_name))

  def testIsInitializedName(self):
    self.assertEqual('IsMy_module1Initialized',
                     gs.PosixStubWriter.IsInitializedName(self.module_name))

  def testInitializeModuleName(self):
    self.assertEqual(
        'InitializeMy_module1',
        gs.PosixStubWriter.InitializeModuleName(self.module_name))

  def testUninitializeModuleName(self):
    self.assertEqual(
        'UninitializeMy_module1',
        gs.PosixStubWriter.UninitializeModuleName(self.module_name))

  def testStubFunctionPointer(self):
    self.assertEqual(
        'static int (*foo_ptr)(int a) = NULL;',
        gs.PosixStubWriter.StubFunctionPointer(SIMPLE_SIGNATURES[0][1]))

  def testStubFunction(self):
    # Test for a signature with a return value and a parameter.
    self.assertEqual("""extern int foo(int a) __attribute__((weak));
int  foo(int a) {
  return foo_ptr(a);
}""", gs.PosixStubWriter.StubFunction(SIMPLE_SIGNATURES[0][1]))

    # Test for a signature with a void return value and no parameters.
    self.assertEqual("""extern void waldo(void) __attribute__((weak));
void  waldo(void) {
  waldo_ptr();
}""", gs.PosixStubWriter.StubFunction(SIMPLE_SIGNATURES[4][1]))

    # Test export macros.
    sig = _MakeSignature('int*', 'foo', ['bool b'])
    sig['export'] = 'TEST_EXPORT'
    self.assertEqual("""extern int* foo(bool b) __attribute__((weak));
int* TEST_EXPORT foo(bool b) {
  return foo_ptr(b);
}""", gs.PosixStubWriter.StubFunction(sig))

  def testWriteImplemenationContents(self):
    outfile = StringIO.StringIO()
    self.writer.WriteImplementationContents('my_namespace', outfile)
    contents = outfile.getvalue()

    # Verify namespace exists somewhere.
    self.assertTrue(contents.find('namespace my_namespace {') != -1)

    # Verify that each signature has an _ptr and a function call in the file.
    # Check that the signatures were exported.
    for sig in self.signatures:
      decl = gs.PosixStubWriter.StubFunctionPointer(sig)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))

    # Verify that each signature has an stub function generated for it.
    for sig in self.signatures:
      decl = gs.PosixStubWriter.StubFunction(sig)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))

    # Find module initializer functions.  Make sure all 3 exist.
    decl = gs.PosixStubWriter.InitializeModuleName(self.module_name)
    self.assertTrue(contents.find(decl) != -1,
                    msg='Expected "%s" in %s' % (decl, contents))
    decl = gs.PosixStubWriter.UninitializeModuleName(self.module_name)
    self.assertTrue(contents.find(decl) != -1,
                    msg='Expected "%s" in %s' % (decl, contents))
    decl = gs.PosixStubWriter.IsInitializedName(self.module_name)
    self.assertTrue(contents.find(decl) != -1,
                    msg='Expected "%s" in %s' % (decl, contents))

  def testWriteHeaderContents(self):
    # Data for header generation.
    module_names = ['oneModule', 'twoModule']

    # Make the header.
    outfile = StringIO.StringIO()
    self.writer.WriteHeaderContents(module_names, 'my_namespace', 'GUARD_',
                                    outfile)
    contents = outfile.getvalue()

    # Check for namespace and header guard.
    self.assertTrue(contents.find('namespace my_namespace {') != -1)
    self.assertTrue(contents.find('#ifndef GUARD_') != -1)

    # Check for umbrella initializer.
    self.assertTrue(contents.find('InitializeStubs(') != -1)

    # Check per-module declarations.
    for name in module_names:
      # Check for enums.
      decl = gs.PosixStubWriter.EnumName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))

      # Check for module initializer functions.
      decl = gs.PosixStubWriter.IsInitializedName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))
      decl = gs.PosixStubWriter.InitializeModuleName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))
      decl = gs.PosixStubWriter.UninitializeModuleName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))

  def testWriteUmbrellaInitializer(self):
    # Data for header generation.
    module_names = ['oneModule', 'twoModule']

    # Make the header.
    outfile = StringIO.StringIO()
    self.writer.WriteUmbrellaInitializer(module_names, 'my_namespace', outfile)
    contents = outfile.getvalue()

    # Check for umbrella initializer declaration.
    self.assertTrue(contents.find('bool InitializeStubs(') != -1)

    # If the umbrella initializer is correctly written, each module will have
    # its initializer called, checked, and uninitialized on failure.  Sanity
    # check that here.
    for name in module_names:
      # Check for module initializer functions.
      decl = gs.PosixStubWriter.IsInitializedName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))
      decl = gs.PosixStubWriter.InitializeModuleName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))
      decl = gs.PosixStubWriter.UninitializeModuleName(name)
      self.assertTrue(contents.find(decl) != -1,
                      msg='Expected "%s" in %s' % (decl, contents))

if __name__ == '__main__':
  unittest.main()