• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import timeit
2import unittest
3import sys
4import io
5from textwrap import dedent
6
7from test.support import captured_stdout
8from test.support import captured_stderr
9
10# timeit's default number of iterations.
11DEFAULT_NUMBER = 1000000
12
13# timeit's default number of repetitions.
14DEFAULT_REPEAT = 5
15
16# XXX: some tests are commented out that would improve the coverage but take a
17# long time to run because they test the default number of loops, which is
18# large.  The tests could be enabled if there was a way to override the default
19# number of loops during testing, but this would require changing the signature
20# of some functions that use the default as a default argument.
21
22class FakeTimer:
23    BASE_TIME = 42.0
24    def __init__(self, seconds_per_increment=1.0):
25        self.count = 0
26        self.setup_calls = 0
27        self.seconds_per_increment=seconds_per_increment
28        timeit._fake_timer = self
29
30    def __call__(self):
31        return self.BASE_TIME + self.count * self.seconds_per_increment
32
33    def inc(self):
34        self.count += 1
35
36    def setup(self):
37        self.setup_calls += 1
38
39    def wrap_timer(self, timer):
40        """Records 'timer' and returns self as callable timer."""
41        self.saved_timer = timer
42        return self
43
44class TestTimeit(unittest.TestCase):
45
46    def tearDown(self):
47        try:
48            del timeit._fake_timer
49        except AttributeError:
50            pass
51
52    def test_reindent_empty(self):
53        self.assertEqual(timeit.reindent("", 0), "")
54        self.assertEqual(timeit.reindent("", 4), "")
55
56    def test_reindent_single(self):
57        self.assertEqual(timeit.reindent("pass", 0), "pass")
58        self.assertEqual(timeit.reindent("pass", 4), "pass")
59
60    def test_reindent_multi_empty(self):
61        self.assertEqual(timeit.reindent("\n\n", 0), "\n\n")
62        self.assertEqual(timeit.reindent("\n\n", 4), "\n    \n    ")
63
64    def test_reindent_multi(self):
65        self.assertEqual(timeit.reindent(
66            "print()\npass\nbreak", 0),
67            "print()\npass\nbreak")
68        self.assertEqual(timeit.reindent(
69            "print()\npass\nbreak", 4),
70            "print()\n    pass\n    break")
71
72    def test_timer_invalid_stmt(self):
73        self.assertRaises(ValueError, timeit.Timer, stmt=None)
74        self.assertRaises(SyntaxError, timeit.Timer, stmt='return')
75        self.assertRaises(SyntaxError, timeit.Timer, stmt='yield')
76        self.assertRaises(SyntaxError, timeit.Timer, stmt='yield from ()')
77        self.assertRaises(SyntaxError, timeit.Timer, stmt='break')
78        self.assertRaises(SyntaxError, timeit.Timer, stmt='continue')
79        self.assertRaises(SyntaxError, timeit.Timer, stmt='from timeit import *')
80        self.assertRaises(SyntaxError, timeit.Timer, stmt='  pass')
81        self.assertRaises(SyntaxError, timeit.Timer,
82                          setup='while False:\n  pass', stmt='  break')
83
84    def test_timer_invalid_setup(self):
85        self.assertRaises(ValueError, timeit.Timer, setup=None)
86        self.assertRaises(SyntaxError, timeit.Timer, setup='return')
87        self.assertRaises(SyntaxError, timeit.Timer, setup='yield')
88        self.assertRaises(SyntaxError, timeit.Timer, setup='yield from ()')
89        self.assertRaises(SyntaxError, timeit.Timer, setup='break')
90        self.assertRaises(SyntaxError, timeit.Timer, setup='continue')
91        self.assertRaises(SyntaxError, timeit.Timer, setup='from timeit import *')
92        self.assertRaises(SyntaxError, timeit.Timer, setup='  pass')
93
94    def test_timer_empty_stmt(self):
95        timeit.Timer(stmt='')
96        timeit.Timer(stmt=' \n\t\f')
97        timeit.Timer(stmt='# comment')
98
99    fake_setup = "import timeit\ntimeit._fake_timer.setup()"
100    fake_stmt = "import timeit\ntimeit._fake_timer.inc()"
101
102    def fake_callable_setup(self):
103        self.fake_timer.setup()
104
105    def fake_callable_stmt(self):
106        self.fake_timer.inc()
107
108    def timeit(self, stmt, setup, number=None, globals=None):
109        self.fake_timer = FakeTimer()
110        t = timeit.Timer(stmt=stmt, setup=setup, timer=self.fake_timer,
111                globals=globals)
112        kwargs = {}
113        if number is None:
114            number = DEFAULT_NUMBER
115        else:
116            kwargs['number'] = number
117        delta_time = t.timeit(**kwargs)
118        self.assertEqual(self.fake_timer.setup_calls, 1)
119        self.assertEqual(self.fake_timer.count, number)
120        self.assertEqual(delta_time, number)
121
122    # Takes too long to run in debug build.
123    #def test_timeit_default_iters(self):
124    #    self.timeit(self.fake_stmt, self.fake_setup)
125
126    def test_timeit_zero_iters(self):
127        self.timeit(self.fake_stmt, self.fake_setup, number=0)
128
129    def test_timeit_few_iters(self):
130        self.timeit(self.fake_stmt, self.fake_setup, number=3)
131
132    def test_timeit_callable_stmt(self):
133        self.timeit(self.fake_callable_stmt, self.fake_setup, number=3)
134
135    def test_timeit_callable_setup(self):
136        self.timeit(self.fake_stmt, self.fake_callable_setup, number=3)
137
138    def test_timeit_callable_stmt_and_setup(self):
139        self.timeit(self.fake_callable_stmt,
140                self.fake_callable_setup, number=3)
141
142    # Takes too long to run in debug build.
143    #def test_timeit_function(self):
144    #    delta_time = timeit.timeit(self.fake_stmt, self.fake_setup,
145    #            timer=FakeTimer())
146    #    self.assertEqual(delta_time, DEFAULT_NUMBER)
147
148    def test_timeit_function_zero_iters(self):
149        delta_time = timeit.timeit(self.fake_stmt, self.fake_setup, number=0,
150                timer=FakeTimer())
151        self.assertEqual(delta_time, 0)
152
153    def test_timeit_globals_args(self):
154        global _global_timer
155        _global_timer = FakeTimer()
156        t = timeit.Timer(stmt='_global_timer.inc()', timer=_global_timer)
157        self.assertRaises(NameError, t.timeit, number=3)
158        timeit.timeit(stmt='_global_timer.inc()', timer=_global_timer,
159                      globals=globals(), number=3)
160        local_timer = FakeTimer()
161        timeit.timeit(stmt='local_timer.inc()', timer=local_timer,
162                      globals=locals(), number=3)
163
164    def repeat(self, stmt, setup, repeat=None, number=None):
165        self.fake_timer = FakeTimer()
166        t = timeit.Timer(stmt=stmt, setup=setup, timer=self.fake_timer)
167        kwargs = {}
168        if repeat is None:
169            repeat = DEFAULT_REPEAT
170        else:
171            kwargs['repeat'] = repeat
172        if number is None:
173            number = DEFAULT_NUMBER
174        else:
175            kwargs['number'] = number
176        delta_times = t.repeat(**kwargs)
177        self.assertEqual(self.fake_timer.setup_calls, repeat)
178        self.assertEqual(self.fake_timer.count, repeat * number)
179        self.assertEqual(delta_times, repeat * [float(number)])
180
181    # Takes too long to run in debug build.
182    #def test_repeat_default(self):
183    #    self.repeat(self.fake_stmt, self.fake_setup)
184
185    def test_repeat_zero_reps(self):
186        self.repeat(self.fake_stmt, self.fake_setup, repeat=0)
187
188    def test_repeat_zero_iters(self):
189        self.repeat(self.fake_stmt, self.fake_setup, number=0)
190
191    def test_repeat_few_reps_and_iters(self):
192        self.repeat(self.fake_stmt, self.fake_setup, repeat=3, number=5)
193
194    def test_repeat_callable_stmt(self):
195        self.repeat(self.fake_callable_stmt, self.fake_setup,
196                repeat=3, number=5)
197
198    def test_repeat_callable_setup(self):
199        self.repeat(self.fake_stmt, self.fake_callable_setup,
200                repeat=3, number=5)
201
202    def test_repeat_callable_stmt_and_setup(self):
203        self.repeat(self.fake_callable_stmt, self.fake_callable_setup,
204                repeat=3, number=5)
205
206    # Takes too long to run in debug build.
207    #def test_repeat_function(self):
208    #    delta_times = timeit.repeat(self.fake_stmt, self.fake_setup,
209    #            timer=FakeTimer())
210    #    self.assertEqual(delta_times, DEFAULT_REPEAT * [float(DEFAULT_NUMBER)])
211
212    def test_repeat_function_zero_reps(self):
213        delta_times = timeit.repeat(self.fake_stmt, self.fake_setup, repeat=0,
214                timer=FakeTimer())
215        self.assertEqual(delta_times, [])
216
217    def test_repeat_function_zero_iters(self):
218        delta_times = timeit.repeat(self.fake_stmt, self.fake_setup, number=0,
219                timer=FakeTimer())
220        self.assertEqual(delta_times, DEFAULT_REPEAT * [0.0])
221
222    def assert_exc_string(self, exc_string, expected_exc_name):
223        exc_lines = exc_string.splitlines()
224        self.assertGreater(len(exc_lines), 2)
225        self.assertTrue(exc_lines[0].startswith('Traceback'))
226        self.assertTrue(exc_lines[-1].startswith(expected_exc_name))
227
228    def test_print_exc(self):
229        s = io.StringIO()
230        t = timeit.Timer("1/0")
231        try:
232            t.timeit()
233        except:
234            t.print_exc(s)
235        self.assert_exc_string(s.getvalue(), 'ZeroDivisionError')
236
237    MAIN_DEFAULT_OUTPUT = "1 loop, best of 5: 1 sec per loop\n"
238
239    def run_main(self, seconds_per_increment=1.0, switches=None, timer=None):
240        if timer is None:
241            timer = FakeTimer(seconds_per_increment=seconds_per_increment)
242        if switches is None:
243            args = []
244        else:
245            args = switches[:]
246        args.append(self.fake_stmt)
247        # timeit.main() modifies sys.path, so save and restore it.
248        orig_sys_path = sys.path[:]
249        with captured_stdout() as s:
250            timeit.main(args=args, _wrap_timer=timer.wrap_timer)
251        sys.path[:] = orig_sys_path[:]
252        return s.getvalue()
253
254    def test_main_bad_switch(self):
255        s = self.run_main(switches=['--bad-switch'])
256        self.assertEqual(s, dedent("""\
257            option --bad-switch not recognized
258            use -h/--help for command line help
259            """))
260
261    def test_main_seconds(self):
262        s = self.run_main(seconds_per_increment=5.5)
263        self.assertEqual(s, "1 loop, best of 5: 5.5 sec per loop\n")
264
265    def test_main_milliseconds(self):
266        s = self.run_main(seconds_per_increment=0.0055)
267        self.assertEqual(s, "50 loops, best of 5: 5.5 msec per loop\n")
268
269    def test_main_microseconds(self):
270        s = self.run_main(seconds_per_increment=0.0000025, switches=['-n100'])
271        self.assertEqual(s, "100 loops, best of 5: 2.5 usec per loop\n")
272
273    def test_main_fixed_iters(self):
274        s = self.run_main(seconds_per_increment=2.0, switches=['-n35'])
275        self.assertEqual(s, "35 loops, best of 5: 2 sec per loop\n")
276
277    def test_main_setup(self):
278        s = self.run_main(seconds_per_increment=2.0,
279                switches=['-n35', '-s', 'print("CustomSetup")'])
280        self.assertEqual(s, "CustomSetup\n" * DEFAULT_REPEAT +
281                "35 loops, best of 5: 2 sec per loop\n")
282
283    def test_main_multiple_setups(self):
284        s = self.run_main(seconds_per_increment=2.0,
285                switches=['-n35', '-s', 'a = "CustomSetup"', '-s', 'print(a)'])
286        self.assertEqual(s, "CustomSetup\n" * DEFAULT_REPEAT +
287                "35 loops, best of 5: 2 sec per loop\n")
288
289    def test_main_fixed_reps(self):
290        s = self.run_main(seconds_per_increment=60.0, switches=['-r9'])
291        self.assertEqual(s, "1 loop, best of 9: 60 sec per loop\n")
292
293    def test_main_negative_reps(self):
294        s = self.run_main(seconds_per_increment=60.0, switches=['-r-5'])
295        self.assertEqual(s, "1 loop, best of 1: 60 sec per loop\n")
296
297    @unittest.skipIf(sys.flags.optimize >= 2, "need __doc__")
298    def test_main_help(self):
299        s = self.run_main(switches=['-h'])
300        # Note: It's not clear that the trailing space was intended as part of
301        # the help text, but since it's there, check for it.
302        self.assertEqual(s, timeit.__doc__ + ' ')
303
304    def test_main_verbose(self):
305        s = self.run_main(switches=['-v'])
306        self.assertEqual(s, dedent("""\
307                1 loop -> 1 secs
308
309                raw times: 1 sec, 1 sec, 1 sec, 1 sec, 1 sec
310
311                1 loop, best of 5: 1 sec per loop
312            """))
313
314    def test_main_very_verbose(self):
315        s = self.run_main(seconds_per_increment=0.000_030, switches=['-vv'])
316        self.assertEqual(s, dedent("""\
317                1 loop -> 3e-05 secs
318                2 loops -> 6e-05 secs
319                5 loops -> 0.00015 secs
320                10 loops -> 0.0003 secs
321                20 loops -> 0.0006 secs
322                50 loops -> 0.0015 secs
323                100 loops -> 0.003 secs
324                200 loops -> 0.006 secs
325                500 loops -> 0.015 secs
326                1000 loops -> 0.03 secs
327                2000 loops -> 0.06 secs
328                5000 loops -> 0.15 secs
329                10000 loops -> 0.3 secs
330
331                raw times: 300 msec, 300 msec, 300 msec, 300 msec, 300 msec
332
333                10000 loops, best of 5: 30 usec per loop
334            """))
335
336    def test_main_with_time_unit(self):
337        unit_sec = self.run_main(seconds_per_increment=0.003,
338                switches=['-u', 'sec'])
339        self.assertEqual(unit_sec,
340                "100 loops, best of 5: 0.003 sec per loop\n")
341        unit_msec = self.run_main(seconds_per_increment=0.003,
342                switches=['-u', 'msec'])
343        self.assertEqual(unit_msec,
344                "100 loops, best of 5: 3 msec per loop\n")
345        unit_usec = self.run_main(seconds_per_increment=0.003,
346                switches=['-u', 'usec'])
347        self.assertEqual(unit_usec,
348                "100 loops, best of 5: 3e+03 usec per loop\n")
349        # Test invalid unit input
350        with captured_stderr() as error_stringio:
351            invalid = self.run_main(seconds_per_increment=0.003,
352                    switches=['-u', 'parsec'])
353        self.assertEqual(error_stringio.getvalue(),
354                    "Unrecognized unit. Please select nsec, usec, msec, or sec.\n")
355
356    def test_main_exception(self):
357        with captured_stderr() as error_stringio:
358            s = self.run_main(switches=['1/0'])
359        self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError')
360
361    def test_main_exception_fixed_reps(self):
362        with captured_stderr() as error_stringio:
363            s = self.run_main(switches=['-n1', '1/0'])
364        self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError')
365
366    def autorange(self, seconds_per_increment=1/1024, callback=None):
367        timer = FakeTimer(seconds_per_increment=seconds_per_increment)
368        t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer)
369        return t.autorange(callback)
370
371    def test_autorange(self):
372        num_loops, time_taken = self.autorange()
373        self.assertEqual(num_loops, 500)
374        self.assertEqual(time_taken, 500/1024)
375
376    def test_autorange_second(self):
377        num_loops, time_taken = self.autorange(seconds_per_increment=1.0)
378        self.assertEqual(num_loops, 1)
379        self.assertEqual(time_taken, 1.0)
380
381    def test_autorange_with_callback(self):
382        def callback(a, b):
383            print("{} {:.3f}".format(a, b))
384        with captured_stdout() as s:
385            num_loops, time_taken = self.autorange(callback=callback)
386        self.assertEqual(num_loops, 500)
387        self.assertEqual(time_taken, 500/1024)
388        expected = ('1 0.001\n'
389                    '2 0.002\n'
390                    '5 0.005\n'
391                    '10 0.010\n'
392                    '20 0.020\n'
393                    '50 0.049\n'
394                    '100 0.098\n'
395                    '200 0.195\n'
396                    '500 0.488\n')
397        self.assertEqual(s.getvalue(), expected)
398
399
400if __name__ == '__main__':
401    unittest.main()
402