• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15# ==============================================================================
16"""TensorFlow Lite Python Interface: Sanity check."""
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import ctypes
22import io
23import sys
24
25from unittest import mock
26
27import numpy as np
28import six
29
30# Force loaded shared object symbols to be globally visible. This is needed so
31# that the interpreter_wrapper, in one .so file, can see the test_registerer,
32# in a different .so file. Note that this may already be set by default.
33# pylint: disable=g-import-not-at-top
34if hasattr(sys, 'setdlopenflags') and hasattr(sys, 'getdlopenflags'):
35  sys.setdlopenflags(sys.getdlopenflags() | ctypes.RTLD_GLOBAL)
36
37from tensorflow.lite.python import interpreter as interpreter_wrapper
38from tensorflow.lite.python.testdata import _pywrap_test_registerer as test_registerer
39from tensorflow.python.framework import test_util
40from tensorflow.python.platform import resource_loader
41from tensorflow.python.platform import test
42try:
43  from tensorflow.lite.python import metrics_portable
44  metrics = metrics_portable
45except ImportError:
46  from tensorflow.lite.python import metrics_nonportable
47  metrics = metrics_nonportable
48# pylint: enable=g-import-not-at-top
49
50
51class InterpreterCustomOpsTest(test_util.TensorFlowTestCase):
52
53  def testRegistererByName(self):
54    interpreter = interpreter_wrapper.InterpreterWithCustomOps(
55        model_path=resource_loader.get_path_to_datafile(
56            'testdata/permute_float.tflite'),
57        custom_op_registerers=['TF_TestRegisterer'])
58    self.assertTrue(interpreter._safe_to_run())
59    self.assertEqual(test_registerer.get_num_test_registerer_calls(), 1)
60
61  def testRegistererByFunc(self):
62    interpreter = interpreter_wrapper.InterpreterWithCustomOps(
63        model_path=resource_loader.get_path_to_datafile(
64            'testdata/permute_float.tflite'),
65        custom_op_registerers=[test_registerer.TF_TestRegisterer])
66    self.assertTrue(interpreter._safe_to_run())
67    self.assertEqual(test_registerer.get_num_test_registerer_calls(), 1)
68
69  def testRegistererFailure(self):
70    bogus_name = 'CompletelyBogusRegistererName'
71    with self.assertRaisesRegex(
72        ValueError, 'Looking up symbol \'' + bogus_name + '\' failed'):
73      interpreter_wrapper.InterpreterWithCustomOps(
74          model_path=resource_loader.get_path_to_datafile(
75              'testdata/permute_float.tflite'),
76          custom_op_registerers=[bogus_name])
77
78  def testNoCustomOps(self):
79    interpreter = interpreter_wrapper.InterpreterWithCustomOps(
80        model_path=resource_loader.get_path_to_datafile(
81            'testdata/permute_float.tflite'))
82    self.assertTrue(interpreter._safe_to_run())
83
84
85class InterpreterTest(test_util.TensorFlowTestCase):
86
87  def assertQuantizationParamsEqual(self, scales, zero_points,
88                                    quantized_dimension, params):
89    self.assertAllEqual(scales, params['scales'])
90    self.assertAllEqual(zero_points, params['zero_points'])
91    self.assertEqual(quantized_dimension, params['quantized_dimension'])
92
93  def testThreads_NegativeValue(self):
94    with self.assertRaisesRegex(ValueError, 'num_threads should >= 1'):
95      interpreter_wrapper.Interpreter(
96          model_path=resource_loader.get_path_to_datafile(
97              'testdata/permute_float.tflite'),
98          num_threads=-1)
99
100  def testThreads_WrongType(self):
101    with self.assertRaisesRegex(ValueError,
102                                'type of num_threads should be int'):
103      interpreter_wrapper.Interpreter(
104          model_path=resource_loader.get_path_to_datafile(
105              'testdata/permute_float.tflite'),
106          num_threads=4.2)
107
108  def testNotSupportedOpResolverTypes(self):
109    with self.assertRaisesRegex(
110        ValueError, 'Unrecognized passed in op resolver type: test'):
111      interpreter_wrapper.Interpreter(
112          model_path=resource_loader.get_path_to_datafile(
113              'testdata/permute_float.tflite'),
114          experimental_op_resolver_type='test')
115
116  def testFloatWithDifferentOpResolverTypes(self):
117    op_resolver_types = [
118        interpreter_wrapper.OpResolverType.BUILTIN,
119        interpreter_wrapper.OpResolverType.BUILTIN_REF,
120        interpreter_wrapper.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES
121    ]
122
123    for op_resolver_type in op_resolver_types:
124      interpreter = interpreter_wrapper.Interpreter(
125          model_path=resource_loader.get_path_to_datafile(
126              'testdata/permute_float.tflite'),
127          experimental_op_resolver_type=op_resolver_type)
128      interpreter.allocate_tensors()
129
130      input_details = interpreter.get_input_details()
131      self.assertEqual(1, len(input_details))
132      self.assertEqual('input', input_details[0]['name'])
133      self.assertEqual(np.float32, input_details[0]['dtype'])
134      self.assertTrue(([1, 4] == input_details[0]['shape']).all())
135      self.assertEqual((0.0, 0), input_details[0]['quantization'])
136      self.assertQuantizationParamsEqual(
137          [], [], 0, input_details[0]['quantization_parameters'])
138
139      output_details = interpreter.get_output_details()
140      self.assertEqual(1, len(output_details))
141      self.assertEqual('output', output_details[0]['name'])
142      self.assertEqual(np.float32, output_details[0]['dtype'])
143      self.assertTrue(([1, 4] == output_details[0]['shape']).all())
144      self.assertEqual((0.0, 0), output_details[0]['quantization'])
145      self.assertQuantizationParamsEqual(
146          [], [], 0, output_details[0]['quantization_parameters'])
147
148      test_input = np.array([[1.0, 2.0, 3.0, 4.0]], dtype=np.float32)
149      expected_output = np.array([[4.0, 3.0, 2.0, 1.0]], dtype=np.float32)
150      interpreter.set_tensor(input_details[0]['index'], test_input)
151      interpreter.invoke()
152
153      output_data = interpreter.get_tensor(output_details[0]['index'])
154      self.assertTrue((expected_output == output_data).all())
155
156  def testFloatWithTwoThreads(self):
157    interpreter = interpreter_wrapper.Interpreter(
158        model_path=resource_loader.get_path_to_datafile(
159            'testdata/permute_float.tflite'),
160        num_threads=2)
161    interpreter.allocate_tensors()
162
163    input_details = interpreter.get_input_details()
164    test_input = np.array([[1.0, 2.0, 3.0, 4.0]], dtype=np.float32)
165    expected_output = np.array([[4.0, 3.0, 2.0, 1.0]], dtype=np.float32)
166    interpreter.set_tensor(input_details[0]['index'], test_input)
167    interpreter.invoke()
168
169    output_details = interpreter.get_output_details()
170    output_data = interpreter.get_tensor(output_details[0]['index'])
171    self.assertTrue((expected_output == output_data).all())
172
173  def testUint8(self):
174    model_path = resource_loader.get_path_to_datafile(
175        'testdata/permute_uint8.tflite')
176    with io.open(model_path, 'rb') as model_file:
177      data = model_file.read()
178
179    interpreter = interpreter_wrapper.Interpreter(model_content=data)
180    interpreter.allocate_tensors()
181
182    input_details = interpreter.get_input_details()
183    self.assertEqual(1, len(input_details))
184    self.assertEqual('input', input_details[0]['name'])
185    self.assertEqual(np.uint8, input_details[0]['dtype'])
186    self.assertTrue(([1, 4] == input_details[0]['shape']).all())
187    self.assertEqual((1.0, 0), input_details[0]['quantization'])
188    self.assertQuantizationParamsEqual(
189        [1.0], [0], 0, input_details[0]['quantization_parameters'])
190
191    output_details = interpreter.get_output_details()
192    self.assertEqual(1, len(output_details))
193    self.assertEqual('output', output_details[0]['name'])
194    self.assertEqual(np.uint8, output_details[0]['dtype'])
195    self.assertTrue(([1, 4] == output_details[0]['shape']).all())
196    self.assertEqual((1.0, 0), output_details[0]['quantization'])
197    self.assertQuantizationParamsEqual(
198        [1.0], [0], 0, output_details[0]['quantization_parameters'])
199
200    test_input = np.array([[1, 2, 3, 4]], dtype=np.uint8)
201    expected_output = np.array([[4, 3, 2, 1]], dtype=np.uint8)
202    interpreter.resize_tensor_input(input_details[0]['index'], test_input.shape)
203    interpreter.allocate_tensors()
204    interpreter.set_tensor(input_details[0]['index'], test_input)
205    interpreter.invoke()
206
207    output_data = interpreter.get_tensor(output_details[0]['index'])
208    self.assertTrue((expected_output == output_data).all())
209
210  def testString(self):
211    interpreter = interpreter_wrapper.Interpreter(
212        model_path=resource_loader.get_path_to_datafile(
213            'testdata/gather_string.tflite'))
214    interpreter.allocate_tensors()
215
216    input_details = interpreter.get_input_details()
217    self.assertEqual(2, len(input_details))
218    self.assertEqual('input', input_details[0]['name'])
219    self.assertEqual(np.string_, input_details[0]['dtype'])
220    self.assertTrue(([10] == input_details[0]['shape']).all())
221    self.assertEqual((0.0, 0), input_details[0]['quantization'])
222    self.assertQuantizationParamsEqual(
223        [], [], 0, input_details[0]['quantization_parameters'])
224    self.assertEqual('indices', input_details[1]['name'])
225    self.assertEqual(np.int64, input_details[1]['dtype'])
226    self.assertTrue(([3] == input_details[1]['shape']).all())
227    self.assertEqual((0.0, 0), input_details[1]['quantization'])
228    self.assertQuantizationParamsEqual(
229        [], [], 0, input_details[1]['quantization_parameters'])
230
231    output_details = interpreter.get_output_details()
232    self.assertEqual(1, len(output_details))
233    self.assertEqual('output', output_details[0]['name'])
234    self.assertEqual(np.string_, output_details[0]['dtype'])
235    self.assertTrue(([3] == output_details[0]['shape']).all())
236    self.assertEqual((0.0, 0), output_details[0]['quantization'])
237    self.assertQuantizationParamsEqual(
238        [], [], 0, output_details[0]['quantization_parameters'])
239
240    test_input = np.array([1, 2, 3], dtype=np.int64)
241    interpreter.set_tensor(input_details[1]['index'], test_input)
242
243    test_input = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'])
244    expected_output = np.array([b'b', b'c', b'd'])
245    interpreter.set_tensor(input_details[0]['index'], test_input)
246    interpreter.invoke()
247
248    output_data = interpreter.get_tensor(output_details[0]['index'])
249    self.assertTrue((expected_output == output_data).all())
250
251  def testStringZeroDim(self):
252    data = b'abcd' + bytes(16)
253    interpreter = interpreter_wrapper.Interpreter(
254        model_path=resource_loader.get_path_to_datafile(
255            'testdata/gather_string_0d.tflite'))
256    interpreter.allocate_tensors()
257
258    input_details = interpreter.get_input_details()
259    interpreter.set_tensor(input_details[0]['index'], np.array(data))
260    test_input_tensor = interpreter.get_tensor(input_details[0]['index'])
261    self.assertEqual(len(data), len(test_input_tensor.item(0)))
262
263  def testPerChannelParams(self):
264    interpreter = interpreter_wrapper.Interpreter(
265        model_path=resource_loader.get_path_to_datafile('testdata/pc_conv.bin'))
266    interpreter.allocate_tensors()
267
268    # Tensor index 1 is the weight.
269    weight_details = interpreter.get_tensor_details()[1]
270    qparams = weight_details['quantization_parameters']
271    # Ensure that we retrieve per channel quantization params correctly.
272    self.assertEqual(len(qparams['scales']), 128)
273
274  def testDenseTensorAccess(self):
275    interpreter = interpreter_wrapper.Interpreter(
276        model_path=resource_loader.get_path_to_datafile('testdata/pc_conv.bin'))
277    interpreter.allocate_tensors()
278    weight_details = interpreter.get_tensor_details()[1]
279    s_params = weight_details['sparsity_parameters']
280    self.assertEqual(s_params, {})
281
282  def testSparseTensorAccess(self):
283    interpreter = interpreter_wrapper.InterpreterWithCustomOps(
284        model_path=resource_loader.get_path_to_datafile(
285            '../testdata/sparse_tensor.bin'),
286        custom_op_registerers=['TF_TestRegisterer'])
287    interpreter.allocate_tensors()
288
289    # Tensor at index 0 is sparse.
290    compressed_buffer = interpreter.get_tensor(0)
291    # Ensure that the buffer is of correct size and value.
292    self.assertEqual(len(compressed_buffer), 12)
293    sparse_value = [1, 0, 0, 4, 2, 3, 0, 0, 5, 0, 0, 6]
294    self.assertAllEqual(compressed_buffer, sparse_value)
295
296    tensor_details = interpreter.get_tensor_details()[0]
297    s_params = tensor_details['sparsity_parameters']
298
299    # Ensure sparsity parameter returned is correct
300    self.assertAllEqual(s_params['traversal_order'], [0, 1, 2, 3])
301    self.assertAllEqual(s_params['block_map'], [0, 1])
302    dense_dim_metadata = {'format': 0, 'dense_size': 2}
303    self.assertAllEqual(s_params['dim_metadata'][0], dense_dim_metadata)
304    self.assertAllEqual(s_params['dim_metadata'][2], dense_dim_metadata)
305    self.assertAllEqual(s_params['dim_metadata'][3], dense_dim_metadata)
306    self.assertEqual(s_params['dim_metadata'][1]['format'], 1)
307    self.assertAllEqual(s_params['dim_metadata'][1]['array_segments'],
308                        [0, 2, 3])
309    self.assertAllEqual(s_params['dim_metadata'][1]['array_indices'], [0, 1, 1])
310
311  @mock.patch.object(metrics.TFLiteMetrics,
312                     'increase_counter_interpreter_creation')
313  def testCreationCounter(self, increase_call):
314    interpreter_wrapper.Interpreter(
315        model_path=resource_loader.get_path_to_datafile(
316            'testdata/permute_float.tflite'))
317    increase_call.assert_called_once()
318
319
320class InterpreterTestErrorPropagation(test_util.TensorFlowTestCase):
321
322  def testInvalidModelContent(self):
323    with self.assertRaisesRegex(ValueError,
324                                'Model provided has model identifier \''):
325      interpreter_wrapper.Interpreter(model_content=six.b('garbage'))
326
327  def testInvalidModelFile(self):
328    with self.assertRaisesRegex(ValueError,
329                                'Could not open \'totally_invalid_file_name\''):
330      interpreter_wrapper.Interpreter(model_path='totally_invalid_file_name')
331
332  def testInvokeBeforeReady(self):
333    interpreter = interpreter_wrapper.Interpreter(
334        model_path=resource_loader.get_path_to_datafile(
335            'testdata/permute_float.tflite'))
336    with self.assertRaisesRegex(RuntimeError,
337                                'Invoke called on model that is not ready'):
338      interpreter.invoke()
339
340  def testInvalidModelFileContent(self):
341    with self.assertRaisesRegex(
342        ValueError, '`model_path` or `model_content` must be specified.'):
343      interpreter_wrapper.Interpreter(model_path=None, model_content=None)
344
345  def testInvalidIndex(self):
346    interpreter = interpreter_wrapper.Interpreter(
347        model_path=resource_loader.get_path_to_datafile(
348            'testdata/permute_float.tflite'))
349    interpreter.allocate_tensors()
350    # Invalid tensor index passed.
351    with self.assertRaisesRegex(ValueError, 'Tensor with no shape found.'):
352      interpreter._get_tensor_details(4)
353    with self.assertRaisesRegex(ValueError, 'Invalid node index'):
354      interpreter._get_op_details(4)
355
356
357class InterpreterTensorAccessorTest(test_util.TensorFlowTestCase):
358
359  def setUp(self):
360    super(InterpreterTensorAccessorTest, self).setUp()
361    self.interpreter = interpreter_wrapper.Interpreter(
362        model_path=resource_loader.get_path_to_datafile(
363            'testdata/permute_float.tflite'))
364    self.interpreter.allocate_tensors()
365    self.input0 = self.interpreter.get_input_details()[0]['index']
366    self.initial_data = np.array([[-1., -2., -3., -4.]], np.float32)
367
368  def testTensorAccessor(self):
369    """Check that tensor returns a reference."""
370    array_ref = self.interpreter.tensor(self.input0)
371    np.copyto(array_ref(), self.initial_data)
372    self.assertAllEqual(array_ref(), self.initial_data)
373    self.assertAllEqual(
374        self.interpreter.get_tensor(self.input0), self.initial_data)
375
376  def testGetTensorAccessor(self):
377    """Check that get_tensor returns a copy."""
378    self.interpreter.set_tensor(self.input0, self.initial_data)
379    array_initial_copy = self.interpreter.get_tensor(self.input0)
380    new_value = np.add(1., array_initial_copy)
381    self.interpreter.set_tensor(self.input0, new_value)
382    self.assertAllEqual(array_initial_copy, self.initial_data)
383    self.assertAllEqual(self.interpreter.get_tensor(self.input0), new_value)
384
385  def testBase(self):
386    self.assertTrue(self.interpreter._safe_to_run())
387    _ = self.interpreter.tensor(self.input0)
388    self.assertTrue(self.interpreter._safe_to_run())
389    in0 = self.interpreter.tensor(self.input0)()
390    self.assertFalse(self.interpreter._safe_to_run())
391    in0b = self.interpreter.tensor(self.input0)()
392    self.assertFalse(self.interpreter._safe_to_run())
393    # Now get rid of the buffers so that we can evaluate.
394    del in0
395    del in0b
396    self.assertTrue(self.interpreter._safe_to_run())
397
398  def testBaseProtectsFunctions(self):
399    in0 = self.interpreter.tensor(self.input0)()
400    # Make sure we get an exception if we try to run an unsafe operation
401    with self.assertRaisesRegex(RuntimeError, 'There is at least 1 reference'):
402      _ = self.interpreter.allocate_tensors()
403    # Make sure we get an exception if we try to run an unsafe operation
404    with self.assertRaisesRegex(RuntimeError, 'There is at least 1 reference'):
405      _ = self.interpreter.invoke()  # pylint: disable=assignment-from-no-return
406    # Now test that we can run
407    del in0  # this is our only buffer reference, so now it is safe to change
408    in0safe = self.interpreter.tensor(self.input0)
409    _ = self.interpreter.allocate_tensors()
410    del in0safe  # make sure in0Safe is held but lint doesn't complain
411
412
413class InterpreterDelegateTest(test_util.TensorFlowTestCase):
414
415  def setUp(self):
416    super(InterpreterDelegateTest, self).setUp()
417    self._delegate_file = resource_loader.get_path_to_datafile(
418        'testdata/test_delegate.so')
419    self._model_file = resource_loader.get_path_to_datafile(
420        'testdata/permute_float.tflite')
421
422    # Load the library to reset the counters.
423    library = ctypes.pydll.LoadLibrary(self._delegate_file)
424    library.initialize_counters()
425
426  def _TestInterpreter(self, model_path, options=None):
427    """Test wrapper function that creates an interpreter with the delegate."""
428    delegate = interpreter_wrapper.load_delegate(self._delegate_file, options)
429    return interpreter_wrapper.Interpreter(
430        model_path=model_path, experimental_delegates=[delegate])
431
432  def testDelegate(self):
433    """Tests the delegate creation and destruction."""
434    interpreter = self._TestInterpreter(model_path=self._model_file)
435    lib = interpreter._delegates[0]._library
436
437    self.assertEqual(lib.get_num_delegates_created(), 1)
438    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
439    self.assertEqual(lib.get_num_delegates_invoked(), 1)
440
441    del interpreter
442
443    self.assertEqual(lib.get_num_delegates_created(), 1)
444    self.assertEqual(lib.get_num_delegates_destroyed(), 1)
445    self.assertEqual(lib.get_num_delegates_invoked(), 1)
446
447  def testMultipleInterpreters(self):
448    delegate = interpreter_wrapper.load_delegate(self._delegate_file)
449    lib = delegate._library
450
451    self.assertEqual(lib.get_num_delegates_created(), 1)
452    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
453    self.assertEqual(lib.get_num_delegates_invoked(), 0)
454
455    interpreter_a = interpreter_wrapper.Interpreter(
456        model_path=self._model_file, experimental_delegates=[delegate])
457
458    self.assertEqual(lib.get_num_delegates_created(), 1)
459    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
460    self.assertEqual(lib.get_num_delegates_invoked(), 1)
461
462    interpreter_b = interpreter_wrapper.Interpreter(
463        model_path=self._model_file, experimental_delegates=[delegate])
464
465    self.assertEqual(lib.get_num_delegates_created(), 1)
466    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
467    self.assertEqual(lib.get_num_delegates_invoked(), 2)
468
469    del delegate
470    del interpreter_a
471
472    self.assertEqual(lib.get_num_delegates_created(), 1)
473    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
474    self.assertEqual(lib.get_num_delegates_invoked(), 2)
475
476    del interpreter_b
477
478    self.assertEqual(lib.get_num_delegates_created(), 1)
479    self.assertEqual(lib.get_num_delegates_destroyed(), 1)
480    self.assertEqual(lib.get_num_delegates_invoked(), 2)
481
482  def testDestructionOrder(self):
483    """Make sure internal _interpreter object is destroyed before delegate."""
484    self.skipTest('TODO(b/142136355): fix flakiness and re-enable')
485    # Track which order destructions were doned in
486    destructions = []
487
488    def register_destruction(x):
489      destructions.append(
490          x if isinstance(x, str) else six.ensure_text(x, 'utf-8'))
491      return 0
492
493    # Make a wrapper for the callback so we can send this to ctypes
494    delegate = interpreter_wrapper.load_delegate(self._delegate_file)
495    # Make an interpreter with the delegate
496    interpreter = interpreter_wrapper.Interpreter(
497        model_path=resource_loader.get_path_to_datafile(
498            'testdata/permute_float.tflite'),
499        experimental_delegates=[delegate])
500
501    class InterpreterDestroyCallback(object):
502
503      def __del__(self):
504        register_destruction('interpreter')
505
506    interpreter._interpreter.stuff = InterpreterDestroyCallback()
507    # Destroy both delegate and interpreter
508    library = delegate._library
509    prototype = ctypes.CFUNCTYPE(ctypes.c_int, (ctypes.c_char_p))
510    library.set_destroy_callback(prototype(register_destruction))
511    del delegate
512    del interpreter
513    library.set_destroy_callback(None)
514    # check the interpreter was destroyed before the delegate
515    self.assertEqual(destructions, ['interpreter', 'test_delegate'])
516
517  def testOptions(self):
518    delegate_a = interpreter_wrapper.load_delegate(self._delegate_file)
519    lib = delegate_a._library
520
521    self.assertEqual(lib.get_num_delegates_created(), 1)
522    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
523    self.assertEqual(lib.get_num_delegates_invoked(), 0)
524    self.assertEqual(lib.get_options_counter(), 0)
525
526    delegate_b = interpreter_wrapper.load_delegate(
527        self._delegate_file, options={
528            'unused': False,
529            'options_counter': 2
530        })
531    lib = delegate_b._library
532
533    self.assertEqual(lib.get_num_delegates_created(), 2)
534    self.assertEqual(lib.get_num_delegates_destroyed(), 0)
535    self.assertEqual(lib.get_num_delegates_invoked(), 0)
536    self.assertEqual(lib.get_options_counter(), 2)
537
538    del delegate_a
539    del delegate_b
540
541    self.assertEqual(lib.get_num_delegates_created(), 2)
542    self.assertEqual(lib.get_num_delegates_destroyed(), 2)
543    self.assertEqual(lib.get_num_delegates_invoked(), 0)
544    self.assertEqual(lib.get_options_counter(), 2)
545
546  def testFail(self):
547    with self.assertRaisesRegex(
548        # Due to exception chaining in PY3, we can't be more specific here and
549        # check that the phrase 'Fail argument sent' is present.
550        ValueError, 'Failed to load delegate from'):
551      interpreter_wrapper.load_delegate(
552          self._delegate_file, options={'fail': 'fail'})
553
554
555if __name__ == '__main__':
556  test.main()
557