CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
test_hashing.py521 linesDownload Raw Back to test
1"""2Test the hashing module.3"""4 5# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>6# Copyright (c) 2009 Gael Varoquaux7# License: BSD Style, 3 clauses.8 9import collections10import gc11import hashlib12import io13import itertools14import pickle15import random16import sys17import time18from concurrent.futures import ProcessPoolExecutor19from decimal import Decimal20 21from joblib.func_inspect import filter_args22from joblib.hashing import hash23from joblib.memory import Memory24from joblib.test.common import np, with_numpy25from joblib.testing import fixture, parametrize, raises, skipif26 27 28def unicode(s):29    return s30 31 32###############################################################################33# Helper functions for the tests34def time_func(func, *args):35    """Time function func on *args."""36    times = list()37    for _ in range(3):38        t1 = time.time()39        func(*args)40        times.append(time.time() - t1)41    return min(times)42 43 44def relative_time(func1, func2, *args):45    """Return the relative time between func1 and func2 applied on46    *args.47    """48    time_func1 = time_func(func1, *args)49    time_func2 = time_func(func2, *args)50    relative_diff = 0.5 * (abs(time_func1 - time_func2) / (time_func1 + time_func2))51    return relative_diff52 53 54class Klass(object):55    def f(self, x):56        return x57 58 59class KlassWithCachedMethod(object):60    def __init__(self, cachedir):61        mem = Memory(location=cachedir)62        self.f = mem.cache(self.f)63 64    def f(self, x):65        return x66 67 68###############################################################################69# Tests70 71input_list = [72    1,73    2,74    1.0,75    2.0,76    1 + 1j,77    2.0 + 1j,78    "a",79    "b",80    (1,),81    (82        1,83        1,84    ),85    [86        1,87    ],88    [89        1,90        1,91    ],92    {1: 1},93    {1: 2},94    {2: 1},95    None,96    gc.collect,97    [98        1,99    ].append,100    # Next 2 sets have unorderable elements in python 3.101    set(("a", 1)),102    set(("a", 1, ("a", 1))),103    # Next 2 dicts have unorderable type of keys in python 3.104    {"a": 1, 1: 2},105    {"a": 1, 1: 2, "d": {"a": 1}},106]107 108 109@parametrize("obj1", input_list)110@parametrize("obj2", input_list)111def test_trivial_hash(obj1, obj2):112    """Smoke test hash on various types."""113    # Check that 2 objects have the same hash only if they are the same.114    are_hashes_equal = hash(obj1) == hash(obj2)115    are_objs_identical = obj1 is obj2116    assert are_hashes_equal == are_objs_identical117 118 119def test_hash_methods():120    # Check that hashing instance methods works121    a = io.StringIO(unicode("a"))122    assert hash(a.flush) == hash(a.flush)123    a1 = collections.deque(range(10))124    a2 = collections.deque(range(9))125    assert hash(a1.extend) != hash(a2.extend)126 127 128@fixture(scope="function")129@with_numpy130def three_np_arrays():131    rnd = np.random.RandomState(0)132    arr1 = rnd.random_sample((10, 10))133    arr2 = arr1.copy()134    arr3 = arr2.copy()135    arr3[0] += 1136    return arr1, arr2, arr3137 138 139def test_hash_numpy_arrays(three_np_arrays):140    arr1, arr2, arr3 = three_np_arrays141 142    for obj1, obj2 in itertools.product(three_np_arrays, repeat=2):143        are_hashes_equal = hash(obj1) == hash(obj2)144        are_arrays_equal = np.all(obj1 == obj2)145        assert are_hashes_equal == are_arrays_equal146 147    assert hash(arr1) != hash(arr1.T)148 149 150def test_hash_numpy_dict_of_arrays(three_np_arrays):151    arr1, arr2, arr3 = three_np_arrays152 153    d1 = {1: arr1, 2: arr2}154    d2 = {1: arr2, 2: arr1}155    d3 = {1: arr2, 2: arr3}156 157    assert hash(d1) == hash(d2)158    assert hash(d1) != hash(d3)159 160 161@with_numpy162@parametrize("dtype", ["datetime64[s]", "timedelta64[D]"])163def test_numpy_datetime_array(dtype):164    # memoryview is not supported for some dtypes e.g. datetime64165    # see https://github.com/joblib/joblib/issues/188 for more details166    a_hash = hash(np.arange(10))167    array = np.arange(0, 10, dtype=dtype)168    assert hash(array) != a_hash169 170 171@with_numpy172def test_hash_numpy_noncontiguous():173    a = np.asarray(np.arange(6000).reshape((1000, 2, 3)), order="F")[:, :1, :]174    b = np.ascontiguousarray(a)175    assert hash(a) != hash(b)176 177    c = np.asfortranarray(a)178    assert hash(a) != hash(c)179 180 181@with_numpy182@parametrize("coerce_mmap", [True, False])183def test_hash_memmap(tmpdir, coerce_mmap):184    """Check that memmap and arrays hash identically if coerce_mmap is True."""185    filename = tmpdir.join("memmap_temp").strpath186    try:187        m = np.memmap(filename, shape=(10, 10), mode="w+")188        a = np.asarray(m)189        are_hashes_equal = hash(a, coerce_mmap=coerce_mmap) == hash(190            m, coerce_mmap=coerce_mmap191        )192        assert are_hashes_equal == coerce_mmap193    finally:194        if "m" in locals():195            del m196            # Force a garbage-collection cycle, to be certain that the197            # object is delete, and we don't run in a problem under198            # Windows with a file handle still open.199            gc.collect()200 201 202@with_numpy203@skipif(204    sys.platform == "win32",205    reason="This test is not stable under windows for some reason",206)207def test_hash_numpy_performance():208    """Check the performance of hashing numpy arrays:209 210    In [22]: a = np.random.random(1000000)211 212    In [23]: %timeit hashlib.md5(a).hexdigest()213    100 loops, best of 3: 20.7 ms per loop214 215    In [24]: %timeit hashlib.md5(pickle.dumps(a, protocol=2)).hexdigest()216    1 loops, best of 3: 73.1 ms per loop217 218    In [25]: %timeit hashlib.md5(cPickle.dumps(a, protocol=2)).hexdigest()219    10 loops, best of 3: 53.9 ms per loop220 221    In [26]: %timeit hash(a)222    100 loops, best of 3: 20.8 ms per loop223    """224    rnd = np.random.RandomState(0)225    a = rnd.random_sample(1000000)226 227    def md5_hash(x):228        return hashlib.md5(memoryview(x)).hexdigest()229 230    relative_diff = relative_time(md5_hash, hash, a)231    assert relative_diff < 0.3232 233    # Check that hashing an tuple of 3 arrays takes approximately234    # 3 times as much as hashing one array235    time_hashlib = 3 * time_func(md5_hash, a)236    time_hash = time_func(hash, (a, a, a))237    relative_diff = 0.5 * (abs(time_hash - time_hashlib) / (time_hash + time_hashlib))238    assert relative_diff < 0.3239 240 241def test_bound_methods_hash():242    """Make sure that calling the same method on two different instances243    of the same class does resolve to the same hashes.244    """245    a = Klass()246    b = Klass()247    assert hash(filter_args(a.f, [], (1,))) == hash(filter_args(b.f, [], (1,)))248 249 250def test_bound_cached_methods_hash(tmpdir):251    """Make sure that calling the same _cached_ method on two different252    instances of the same class does resolve to the same hashes.253    """254    a = KlassWithCachedMethod(tmpdir.strpath)255    b = KlassWithCachedMethod(tmpdir.strpath)256    assert hash(filter_args(a.f.func, [], (1,))) == hash(257        filter_args(b.f.func, [], (1,))258    )259 260 261@with_numpy262def test_hash_object_dtype():263    """Make sure that ndarrays with dtype `object' hash correctly."""264 265    a = np.array([np.arange(i) for i in range(6)], dtype=object)266    b = np.array([np.arange(i) for i in range(6)], dtype=object)267 268    assert hash(a) == hash(b)269 270 271@with_numpy272def test_numpy_scalar():273    # Numpy scalars are built from compiled functions, and lead to274    # strange pickling paths explored, that can give hash collisions275    a = np.float64(2.0)276    b = np.float64(3.0)277    assert hash(a) != hash(b)278 279 280def test_dict_hash(tmpdir):281    # Check that dictionaries hash consistently, even though the ordering282    # of the keys is not guaranteed283    k = KlassWithCachedMethod(tmpdir.strpath)284 285    d = {286        "#s12069__c_maps.nii.gz": [33],287        "#s12158__c_maps.nii.gz": [33],288        "#s12258__c_maps.nii.gz": [33],289        "#s12277__c_maps.nii.gz": [33],290        "#s12300__c_maps.nii.gz": [33],291        "#s12401__c_maps.nii.gz": [33],292        "#s12430__c_maps.nii.gz": [33],293        "#s13817__c_maps.nii.gz": [33],294        "#s13903__c_maps.nii.gz": [33],295        "#s13916__c_maps.nii.gz": [33],296        "#s13981__c_maps.nii.gz": [33],297        "#s13982__c_maps.nii.gz": [33],298        "#s13983__c_maps.nii.gz": [33],299    }300 301    a = k.f(d)302    b = k.f(a)303 304    assert hash(a) == hash(b)305 306 307def test_set_hash(tmpdir):308    # Check that sets hash consistently, even though their ordering309    # is not guaranteed310    k = KlassWithCachedMethod(tmpdir.strpath)311 312    s = set(313        [314            "#s12069__c_maps.nii.gz",315            "#s12158__c_maps.nii.gz",316            "#s12258__c_maps.nii.gz",317            "#s12277__c_maps.nii.gz",318            "#s12300__c_maps.nii.gz",319            "#s12401__c_maps.nii.gz",320            "#s12430__c_maps.nii.gz",321            "#s13817__c_maps.nii.gz",322            "#s13903__c_maps.nii.gz",323            "#s13916__c_maps.nii.gz",324            "#s13981__c_maps.nii.gz",325            "#s13982__c_maps.nii.gz",326            "#s13983__c_maps.nii.gz",327        ]328    )329 330    a = k.f(s)331    b = k.f(a)332 333    assert hash(a) == hash(b)334 335 336def test_set_decimal_hash():337    # Check that sets containing decimals hash consistently, even though338    # ordering is not guaranteed339    assert hash(set([Decimal(0), Decimal("NaN")])) == hash(340        set([Decimal("NaN"), Decimal(0)])341    )342 343 344def test_string():345    # Test that we obtain the same hash for object owning several strings,346    # whatever the past of these strings (which are immutable in Python)347    string = "foo"348    a = {string: "bar"}349    b = {string: "bar"}350    c = pickle.loads(pickle.dumps(b))351    assert hash([a, b]) == hash([a, c])352 353 354@with_numpy355def test_numpy_dtype_pickling():356    # numpy dtype hashing is tricky to get right: see #231, #239, #251 #1080,357    # #1082, and explanatory comments inside358    # ``joblib.hashing.NumpyHasher.save``.359 360    # In this test, we make sure that the pickling of numpy dtypes is robust to361    # object identity and object copy.362 363    dt1 = np.dtype("f4")364    dt2 = np.dtype("f4")365 366    # simple dtypes objects are interned367    assert dt1 is dt2368    assert hash(dt1) == hash(dt2)369 370    dt1_roundtripped = pickle.loads(pickle.dumps(dt1))371    assert dt1 is not dt1_roundtripped372    assert hash(dt1) == hash(dt1_roundtripped)373 374    assert hash([dt1, dt1]) == hash([dt1_roundtripped, dt1_roundtripped])375    assert hash([dt1, dt1]) == hash([dt1, dt1_roundtripped])376 377    complex_dt1 = np.dtype([("name", np.str_, 16), ("grades", np.float64, (2,))])378    complex_dt2 = np.dtype([("name", np.str_, 16), ("grades", np.float64, (2,))])379 380    # complex dtypes objects are not interned381    assert hash(complex_dt1) == hash(complex_dt2)382 383    complex_dt1_roundtripped = pickle.loads(pickle.dumps(complex_dt1))384    assert complex_dt1_roundtripped is not complex_dt1385    assert hash(complex_dt1) == hash(complex_dt1_roundtripped)386 387    assert hash([complex_dt1, complex_dt1]) == hash(388        [complex_dt1_roundtripped, complex_dt1_roundtripped]389    )390    assert hash([complex_dt1, complex_dt1]) == hash(391        [complex_dt1_roundtripped, complex_dt1]392    )393 394 395@parametrize(396    "to_hash,expected",397    [398        ("This is a string to hash", "71b3f47df22cb19431d85d92d0b230b2"),399        ("C'est l\xe9t\xe9", "2d8d189e9b2b0b2e384d93c868c0e576"),400        ((123456, 54321, -98765), "e205227dd82250871fa25aa0ec690aa3"),401        (402            [random.Random(42).random() for _ in range(5)],403            "a11ffad81f9682a7d901e6edc3d16c84",404        ),405        ({"abcde": 123, "sadfas": [-9999, 2, 3]}, "aeda150553d4bb5c69f0e69d51b0e2ef"),406    ],407)408def test_hashes_stay_the_same(to_hash, expected):409    # We want to make sure that hashes don't change with joblib410    # version. For end users, that would mean that they have to411    # regenerate their cache from scratch, which potentially means412    # lengthy recomputations.413    # Expected results have been generated with joblib 0.9.2414    assert hash(to_hash) == expected415 416 417@with_numpy418def test_hashes_are_different_between_c_and_fortran_contiguous_arrays():419    # We want to be sure that the c-contiguous and f-contiguous versions of the420    # same array produce 2 different hashes.421    rng = np.random.RandomState(0)422    arr_c = rng.random_sample((10, 10))423    arr_f = np.asfortranarray(arr_c)424    assert hash(arr_c) != hash(arr_f)425 426 427@with_numpy428def test_0d_array():429    hash(np.array(0))430 431 432@with_numpy433def test_0d_and_1d_array_hashing_is_different():434    assert hash(np.array(0)) != hash(np.array([0]))435 436 437@with_numpy438def test_hashes_stay_the_same_with_numpy_objects():439    # Note: joblib used to test numpy objects hashing by comparing the produced440    # hash of an object with some hard-coded target value to guarantee that441    # hashing remains the same across joblib versions. However, since numpy442    # 1.20 and joblib 1.0, joblib relies on potentially unstable implementation443    # details of numpy to hash np.dtype objects, which makes the stability of444    # hash values across different environments hard to guarantee and to test.445    # As a result, hashing stability across joblib versions becomes best-effort446    # only, and we only test the consistency within a single environment by447    # making sure:448    # - the hash of two copies of the same objects is the same449    # - hashing some object in two different python processes produces the same450    #   value. This should be viewed as a proxy for testing hash consistency451    #   through time between Python sessions (provided no change in the452    #   environment was done between sessions).453 454    def create_objects_to_hash():455        rng = np.random.RandomState(42)456        # Being explicit about dtypes in order to avoid457        # architecture-related differences. Also using 'f4' rather than458        # 'f8' for float arrays because 'f8' arrays generated by459        # rng.random.randn don't seem to be bit-identical on 32bit and460        # 64bit machines.461        to_hash_list = [462            rng.randint(-1000, high=1000, size=50).astype("<i8"),463            tuple(rng.randn(3).astype("<f4") for _ in range(5)),464            [rng.randn(3).astype("<f4") for _ in range(5)],465            {466                -3333: rng.randn(3, 5).astype("<f4"),467                0: [468                    rng.randint(10, size=20).astype("<i8"),469                    rng.randn(10).astype("<f4"),470                ],471            },472            # Non regression cases for473            # https://github.com/joblib/joblib/issues/308474            np.arange(100, dtype="<i8").reshape((10, 10)),475            # Fortran contiguous array476            np.asfortranarray(np.arange(100, dtype="<i8").reshape((10, 10))),477            # Non contiguous array478            np.arange(100, dtype="<i8").reshape((10, 10))[:, :2],479        ]480        return to_hash_list481 482    # Create two lists containing copies of the same objects.  joblib.hash483    # should return the same hash for to_hash_list_one[i] and484    # to_hash_list_two[i]485    to_hash_list_one = create_objects_to_hash()486    to_hash_list_two = create_objects_to_hash()487 488    e1 = ProcessPoolExecutor(max_workers=1)489    e2 = ProcessPoolExecutor(max_workers=1)490 491    try:492        for obj_1, obj_2 in zip(to_hash_list_one, to_hash_list_two):493            # testing consistency of hashes across python processes494            hash_1 = e1.submit(hash, obj_1).result()495            hash_2 = e2.submit(hash, obj_1).result()496            assert hash_1 == hash_2497 498            # testing consistency when hashing two copies of the same objects.499            hash_3 = e1.submit(hash, obj_2).result()500            assert hash_1 == hash_3501 502    finally:503        e1.shutdown()504        e2.shutdown()505 506 507def test_hashing_pickling_error():508    def non_picklable():509        return 42510 511    with raises(pickle.PicklingError) as excinfo:512        hash(non_picklable)513    excinfo.match("PicklingError while hashing")514 515 516def test_wrong_hash_name():517    msg = "Valid options for 'hash_name' are"518    with raises(ValueError, match=msg):519        data = {"foo": "bar"}520        hash(data, hash_name="invalid")521