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