testing.py 3.29 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2017-2019  Nexedi SA and Contributors.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#                          Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
20
"""Package testing mirrors Go testing package for things missed in Python."""
21

22
from __future__ import print_function, absolute_import
23

24
from golang import time
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
from math import ceil, log10


# B is benchmarking timer/request passed to benchmarks as fixture
# similar to https://golang.org/pkg/testing/#B.
#
# Use benchmark to actually run a benchmark.
class B:
    # .N    number of iterations benchmarked function should do

    def __init__(self):
        self.N = 1              # preset to 1 iteration
        self._t_start = None    # t of timer started; None if timer is currently stopped
        self.reset_timer()

    def reset_timer(self):
        self._t_total = 0.

    def start_timer(self):
        if self._t_start is not None:
            return

47
        self._t_start = time.now()
48 49 50 51 52

    def stop_timer(self):
        if self._t_start is None:
            return

53
        t = time.now()
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
        self._t_total += t - self._t_start
        self._t_start = None

    def total_time(self):
        return self._t_total


# BenchmarkResult represent
class BenchmarkResult:
    # .N    number of iterations
    # .T    total time taken
    #
    # TODO memalloc stats
    def __init__(self, n, t):
        self.N, self.T = n, t


# _stopBenchmark, when returned by benchmarked function, tells benchmark driver
# to stop benchmarking this function.
#
# it is private since only py.bench uses it, and noone else should.
# (py.bench uses it to benchmark functions that do not take b as argument)
_stopBenchmark = object()

# benchmark benchmarks benchf auto-adjusting whole running time to ttarget.
#
# benchf is invoked as benchf(b).
def benchmark(benchf, ttarget = 1.):	# -> BenchmarkResult
    b = B()
    b.N = 0
    t = 0.
    while t < (ttarget * 0.9):
        if b.N == 0:
            b.N = 1
        else:
            n = b.N * (ttarget / t)     # exact how to adjust b.N to reach ttarget
            order = int(log10(n))       # n = k·10^order, k ∈ [1,10)
            k = float(n) / (10**order)
            k = ceil(k)                 # lift up k to nearest int
            b.N = int(k * 10**order)    # b.N = int([1,10))·10^order

        b.reset_timer()
        b.start_timer()
        x = benchf(b)
        b.stop_timer()
        t = b.total_time()

        # stop trying to reach ttarget if benchf asks us so.
        if x is _stopBenchmark:
            break

    return BenchmarkResult(b.N, t)