From ef59596ba6df822b9373c567f0397c0860a81b29 Mon Sep 17 00:00:00 2001 From: Akos Kiss Date: Fri, 14 Jul 2023 14:42:04 +0200 Subject: [PATCH] Add new config tuple cache (#34) It uses a dictionary of tuples as the underlying data structure, contrary to the classic config cache that uses a tree. --- picire/cache.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_api.py | 16 ++++++++-------- tests/test_cli.py | 4 ++-- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/picire/cache.py b/picire/cache.py index 59401a4..cc517f1 100644 --- a/picire/cache.py +++ b/picire/cache.py @@ -184,6 +184,50 @@ def _str(p): return ''.join(s) +@CacheRegistry.register('config-tuple') +class ConfigTupleCache(OutcomeCache): + """ + This cache associates configurations (i.e., lists of elements) with their + test outcomes, using a dictionary of tuples as the underlying data + structure. + """ + + def __init__(self, *, cache_fail=False, evict_after_fail=True): + """ + :param cache_fail: Add configurations with FAIL outcome to the cache. + :param evict_after_fail: When a configuration with a FAIL outcome is + added to the cache, evict all larger configurations. + """ + # NOTE: evict_after_fail=True should be safe as after a fail is found, + # reduction continues from there, generating only even smaller test + # cases, and larger tests are never re-tested again. + self._cache_fail = cache_fail + self._evict_after_fail = evict_after_fail + self._container = {} + + def set_test_builder(self, test_builder): + pass + + def add(self, config, result): + if result is Outcome.PASS or self._cache_fail: + self._container[tuple(config)] = result + + if result is Outcome.FAIL and self._evict_after_fail: + length = len(config) + evicted = [c for c in self._container if len(c) > length] + for c in evicted: + del self._container[c] + + def lookup(self, config): + return self._container.get(tuple(config), None) + + def clear(self): + self._container = {} + + def __str__(self): + return '{\n%s}' % ''.join(f'\t{c!r}: {r.name!r},\n' for c, r in sorted(self._container.items())) + + @CacheRegistry.register('content') class ContentCache(OutcomeCache): """ diff --git a/tests/test_api.py b/tests/test_api.py index 4caa084..39e198d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -85,20 +85,20 @@ def _run_picire(self, interesting, config, expect, granularity, dd, split, subse @pytest.mark.parametrize('split, subset_first, subset_iterator, complement_iterator, cache', [ (picire.splitter.BalancedSplit, True, picire.iterator.forward, picire.iterator.forward, picire.cache.NoCache), (picire.splitter.ZellerSplit, True, picire.iterator.forward, picire.iterator.backward, picire.cache.ConfigCache), - (picire.splitter.BalancedSplit, False, picire.iterator.backward, picire.iterator.forward, picire.cache.NoCache), - (picire.splitter.ZellerSplit, False, picire.iterator.backward, picire.iterator.backward, picire.cache.ConfigCache), - (picire.splitter.BalancedSplit, True, picire.iterator.skip, picire.iterator.forward, picire.cache.NoCache), - (picire.splitter.ZellerSplit, True, picire.iterator.skip, picire.iterator.backward, picire.cache.ConfigCache), + (picire.splitter.BalancedSplit, False, picire.iterator.backward, picire.iterator.forward, picire.cache.ConfigTupleCache), + (picire.splitter.ZellerSplit, False, picire.iterator.backward, picire.iterator.backward, picire.cache.NoCache), + (picire.splitter.BalancedSplit, True, picire.iterator.skip, picire.iterator.forward, picire.cache.ConfigCache), + (picire.splitter.ZellerSplit, True, picire.iterator.skip, picire.iterator.backward, picire.cache.ConfigTupleCache), ]) def test_dd(self, interesting, config, expect, granularity, split, subset_first, subset_iterator, complement_iterator, cache): self._run_picire(interesting, config, expect, granularity, picire.DD, split, subset_first, subset_iterator, complement_iterator, cache) @pytest.mark.parametrize('split, subset_first, subset_iterator, complement_iterator, cache', [ (picire.splitter.ZellerSplit, False, picire.iterator.forward, picire.iterator.forward, picire.cache.ConfigCache), - (picire.splitter.BalancedSplit, False, picire.iterator.forward, picire.iterator.backward, picire.cache.NoCache), - (picire.splitter.ZellerSplit, True, picire.iterator.backward, picire.iterator.forward, picire.cache.ConfigCache), - (picire.splitter.BalancedSplit, True, picire.iterator.backward, picire.iterator.backward, picire.cache.NoCache), - (picire.splitter.ZellerSplit, False, picire.iterator.skip, picire.iterator.forward, picire.cache.ConfigCache), + (picire.splitter.BalancedSplit, False, picire.iterator.forward, picire.iterator.backward, picire.cache.ConfigTupleCache), + (picire.splitter.ZellerSplit, True, picire.iterator.backward, picire.iterator.forward, picire.cache.NoCache), + (picire.splitter.BalancedSplit, True, picire.iterator.backward, picire.iterator.backward, picire.cache.ConfigCache), + (picire.splitter.ZellerSplit, False, picire.iterator.skip, picire.iterator.forward, picire.cache.ConfigTupleCache), (picire.splitter.BalancedSplit, False, picire.iterator.skip, picire.iterator.backward, picire.cache.NoCache), ]) def test_parallel(self, interesting, config, expect, granularity, split, subset_first, subset_iterator, complement_iterator, cache): diff --git a/tests/test_cli.py b/tests/test_cli.py index 40fc95f..46bd19d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -52,7 +52,7 @@ def _run_picire(self, test, inp, exp, tmpdir, args): ('--split=balanced', '--subset-iterator=forward', '--complement-iterator=forward', '--cache=config'), ('--split=zeller', '--subset-iterator=forward', '--complement-iterator=backward', '--cache=content'), ('--split=balanced', '--complement-first', '--subset-iterator=backward', '--complement-iterator=forward', '--cache=content-hash'), - ('--split=zeller', '--complement-first', '--subset-iterator=backward', '--complement-iterator=backward', '--cache=config', '--cache-fail', '--no-cache-evict-after-fail'), + ('--split=zeller', '--complement-first', '--subset-iterator=backward', '--complement-iterator=backward', '--cache=config-tuple', '--cache-fail', '--no-cache-evict-after-fail'), ('--split=balanced', '--subset-iterator=skip', '--complement-iterator=forward', '--cache=content', '--cache-fail', '--no-cache-evict-after-fail'), ('--split=zeller', '--subset-iterator=skip', '--complement-iterator=backward', '--cache=content-hash', '--cache-fail', '--no-cache-evict-after-fail'), ]) @@ -60,7 +60,7 @@ def test_dd(self, test, inp, exp, tmpdir, args_atom, args): self._run_picire(test, inp, exp, tmpdir, args_atom + args) @pytest.mark.parametrize('args', [ - ('--split=zeller', '--complement-first', '--subset-iterator=forward', '--complement-iterator=forward', '--cache=config'), + ('--split=zeller', '--complement-first', '--subset-iterator=forward', '--complement-iterator=forward', '--cache=config-tuple'), ('--split=balanced', '--complement-first', '--subset-iterator=forward', '--complement-iterator=backward', '--cache=content'), ('--split=zeller', '--subset-iterator=backward', '--complement-iterator=forward', '--cache=content-hash'), ('--split=balanced', '--subset-iterator=backward', '--complement-iterator=backward', '--cache=config', '--cache-fail', '--no-cache-evict-after-fail'),