def new_buffer(self): buffer = os.urandom( self.fill_size) + bytes(self.settings.buffer_size - self.fill_size) self.last_data = TestData(buffer) self.test_function(self.last_data) self.last_data.freeze() self.update_fill_size()
def incorporate_new_buffer(self, buffer: bytes) -> bool: if buffer[: self.last_data.index] == self.last_data.buffer[: self.last_data.index]: return False data = TestData(buffer) self.test_function(data) data.freeze() if self.consider_new_test_data(data): if self.last_data.status == Status.INTERESTING: self.shrinks += 1 self.last_data = data self.changed += 1 if self.shrinks >= self.settings.max_shrinks: raise StopShrinking() return True return False
def find(draw, check, settings=None): seen = False def test_function(data): nonlocal seen value = draw(data) try: if DEBUG: print(data.buffer[:data.index], "->", value) if check(value): seen = True data.incur_cost(len(repr(data))) data.mark_interesting() elif not seen and DEBUG: print(data.buffer[:data.index], "->", value) except UnsatisfiedAssumption: data.mark_invalid() buffer = find_interesting_buffer( test_function, settings=settings or Settings( generations=5000, )) if buffer is not None: result = draw(TestData(buffer)) assert check(result) return result raise NoSuchExample()
def incorporate_new_buffer(self, buffer: bytes) -> bool: if buffer == self.last_data.buffer: return False data = TestData(buffer) self.test_function(data) data.freeze() if self.consider_new_test_data(data): if self.last_data.status == Status.INTERESTING: self.shrinks += 1 self.last_data = data self.update_fill_size() self.changed += 1 if self.shrinks >= self.settings.max_shrinks: raise StopShrinking() return True return False
def find(draw, check, settings=None): def test_function(data): value = draw(data) if check(value): data.incur_cost(len(repr(data))) if DEBUG: print(data.buffer[:data.index], "->", value) data.mark_interesting() buffer = find_interesting_buffer( test_function, settings=settings or Settings( mutations=50, generations=500, )) if buffer is not None: result = draw(TestData(buffer)) assert check(result) return result raise NoSuchExample()
def new_buffer(self): buffer = self.rand_bytes(self.settings.buffer_size) self.last_data = TestData(buffer) self.test_function(self.last_data) self.last_data.freeze()
class TestRunner(object): def __init__( self, test_function: typing.Callable[[TestData], type(None)], settings: typing.Optional[Settings]=None, ): self._test_function = test_function self.settings = settings or Settings() self.last_data = None self.changed = 0 self.shrinks = 0 self.random = Random() def new_buffer(self): buffer = self.rand_bytes(self.settings.buffer_size) self.last_data = TestData(buffer) self.test_function(self.last_data) self.last_data.freeze() def test_function(self, data): try: self._test_function(data) except StopTest: pass def consider_new_test_data( self, data: TestData ) -> bool: # Transition rules: # 1. Transition cannot decrease the status # 2. Any transition which increases the status is valid # 3. If the previous status was interesting, only shrinking # transitions are allowed. if self.last_data.status < data.status: return True if self.last_data.status > data.status: return False if data.status == Status.INVALID: return data.index >= self.last_data.index if data.status == Status.OVERRUN: return data.index <= self.last_data.index if data.status == Status.INTERESTING: assert len(data.buffer) <= len(self.last_data.buffer) if len(data.buffer) == len(self.last_data.buffer): assert data.buffer < self.last_data.buffer return interest_key(data) < interest_key(self.last_data) return True def incorporate_new_buffer( self, buffer: bytes ) -> bool: if ( buffer[:self.last_data.index] == self.last_data.buffer[:self.last_data.index] ): return False data = TestData(buffer) self.test_function(data) data.freeze() if self.consider_new_test_data(data): if self.last_data.status == Status.INTERESTING: self.shrinks += 1 self.last_data = data self.changed += 1 if self.shrinks >= self.settings.max_shrinks: raise StopShrinking() return True return False def run(self): try: self._run() except StopShrinking: pass def _run(self): self.new_buffer() mutations = 0 generation = 0 while self.last_data.status != Status.INTERESTING: if mutations >= self.settings.mutations: generation += 1 if generation >= self.settings.generations: return mutations = 0 self.new_buffer() else: self.incorporate_new_buffer( self.mutate_data_to_new_buffer() ) mutations += 1 for c in range(256): if self.incorporate_new_buffer(bytes( min(c, b) for b in self.last_data.buffer )): break initial_changes = self.changed change_counter = -1 while ( initial_changes + self.settings.max_shrinks >= self.changed > change_counter ): assert self.last_data.status == Status.INTERESTING change_counter = self.changed interval_change_counter = -1 while self.changed > interval_change_counter: interval_change_counter = self.changed i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): i += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] self.incorporate_new_buffer( self.last_data.buffer[:u] + bytes(sorted(self.last_data.buffer[u:v])) + self.last_data.buffer[v:] ) i += 1 local_changes = -1 while local_changes < self.changed: local_changes = self.changed for c in range(1, 256): buf = self.last_data.buffer if buf.count(c) > 1: if self.incorporate_new_buffer(bytes( c - 1 if b == c else b for b in buf )): buf = self.last_data.buffer for d in range(c): if self.incorporate_new_buffer(bytes( d if b == c - 1 else b for b in buf )): break k = 8 for i in range(len(self.last_data.buffer) - k): buf = self.last_data.buffer if i + k > len(buf): break self.incorporate_new_buffer( buf[:i] + bytes(k) + buf[i + k:] ) i = 0 while i < len(self.last_data.buffer): buf = self.last_data.buffer if not self.incorporate_new_buffer( buf[:i] + buf[i+1:] ): for c in range(buf[i]): if self.incorporate_new_buffer( buf[:i] + bytes([c]) + buf[i+1:] ): break elif self.incorporate_new_buffer( buf[:i] + bytes([c]) + self.rand_bytes(( len(buf) - i - 1)) ): break i += 1 i = 0 while i + 1 < len(self.last_data.buffer): j = i + 1 buf = self.last_data.buffer if buf[i] > buf[j]: self.incorporate_new_buffer( buf[:i] + bytes([buf[j], buf[i]]) + buf[j + 1:] ) i += 1 if self.changed > change_counter: continue i = 0 while i < len(self.last_data.buffer): buf = self.last_data.buffer if not self.incorporate_new_buffer( buf[:i] + buf[i+1:] ): if buf[i] == 0: buf = bytearray(buf) j = i while j >= 0: if buf[j] > 0: buf[j] -= 1 self.incorporate_new_buffer(bytes(buf)) break else: buf[j] = 255 j -= 1 i += 1 if self.changed > change_counter: continue buckets = [[] for _ in range(256)] for i, c in enumerate(self.last_data.buffer): buckets[c].append(i) indices = [] for bucket in buckets: if len(bucket) > 1: indices.extend( (j, k) for j in bucket for k in bucket if j < k ) for j, k in indices: buf = self.last_data.buffer if k >= len(buf): continue if buf[j] == buf[k]: c = buf[j] if c == 0: if j > 0 and buf[j - 1] > 0 and buf[k - 1] > 0: self.incorporate_new_buffer( buf[:j - 1] + bytes([buf[j - 1] - 1, 255]) + buf[j+1:k-1] + bytes([buf[k - 1] - 1, 255]) + buf[k+1:] ) c = buf[j] if c > 0: bd = bytes([c - 1]) if self.incorporate_new_buffer( buf[:j] + bd + buf[j+1:k] + bd + buf[k+1:] ): for d in range(c - 1): buf = self.last_data.buffer bd = bytes([d]) if self.incorporate_new_buffer( buf[:j] + bd + buf[j+1:k] + bd + buf[k+1:] ): break if self.changed > change_counter: continue buf = self.last_data.buffer for j in range(len(buf)): buf = self.last_data.buffer if j >= len(buf): break if buf[j] == 0: continue for k in range(j + 1, len(buf)): buf = self.last_data.buffer if k >= len(buf): break if buf[j] > buf[k]: self.incorporate_new_buffer( buf[:j] + bytes([buf[k]]) + buf[j+1:k] + bytes([buf[j]]) + buf[k+1:] ) buf = self.last_data.buffer if k >= len(buf): break if buf[j] > 0 and buf[k] > 0 and buf[j] != buf[k]: if self.incorporate_new_buffer( buf[:j] + bytes([buf[j] - 1]) + buf[j+1:k] + bytes([buf[k] - 1]) + buf[k+1:] ): break if buf[j] == 0: break for t in range(256): if self.incorporate_new_buffer( buf[:j] + bytes([buf[j] - 1]) + buf[j+1:k] + bytes([t]) + buf[k+1:] ): break def mutate_data_to_new_buffer(self): n = min(len(self.last_data.buffer), self.last_data.index) if not n: return b'' if n == 1: return self.rand_bytes(1) if self.last_data.status == Status.OVERRUN: result = bytearray(self.last_data.buffer) for i, c in enumerate(self.last_data.buffer): t = self.random.randint(0, 2) if t == 0: result[i] = 0 elif t == 1: result[i] = self.random.randint(0, c) else: result[i] = c probe = self.random.randint(0, 255) if probe <= 200 or len(self.last_data.intervals) <= 1: c = self.random.randint(0, 2) i = self.random.randint(0, self.last_data.index - 1) result = bytearray(self.last_data.buffer) if c == 0: result[i] ^= (1 << self.random.randint(0, 7)) elif c == 1: result[i] = 0 else: result[i] = 255 return bytes(result) else: int1 = None int2 = None while int1 == int2: i = self.random.randint(0, len(self.last_data.intervals) - 2) int1 = self.last_data.intervals[i] int2 = self.last_data.intervals[ self.random.randint( i + 1, len(self.last_data.intervals) - 1)] return self.last_data.buffer[:int1[0]] + \ self.last_data.buffer[int2[0]:int2[1]] + \ self.last_data.buffer[int1[1]:] def rand_bytes(self, n): if n == 0: return b'' return self.random.getrandbits(n * 8).to_bytes(n, 'big')
class TestRunner(object): def __init__( self, test_function: typing.Callable[[TestData], type(None)], settings: typing.Optional[Settings] = None ): self._test_function = test_function self.settings = settings or Settings() self.last_data = None self.changed = 0 self.shrinks = 0 self.random = Random() def new_buffer(self): buffer = self.rand_bytes(self.settings.buffer_size) self.last_data = TestData(buffer) self.test_function(self.last_data) self.last_data.freeze() def test_function(self, data): try: self._test_function(data) except StopTest: pass def consider_new_test_data(self, data: TestData) -> bool: # Transition rules: # 1. Transition cannot decrease the status # 2. Any transition which increases the status is valid # 3. If the previous status was interesting, only shrinking # transitions are allowed. if self.last_data.status < data.status: return True if self.last_data.status > data.status: return False if data.status == Status.INVALID: return data.index >= self.last_data.index if data.status == Status.OVERRUN: return data.index <= self.last_data.index if data.status == Status.INTERESTING: assert len(data.buffer) <= len(self.last_data.buffer) if len(data.buffer) == len(self.last_data.buffer): assert data.buffer < self.last_data.buffer return interest_key(data) < interest_key(self.last_data) return True def incorporate_new_buffer(self, buffer: bytes) -> bool: if buffer[: self.last_data.index] == self.last_data.buffer[: self.last_data.index]: return False data = TestData(buffer) self.test_function(data) data.freeze() if self.consider_new_test_data(data): if self.last_data.status == Status.INTERESTING: self.shrinks += 1 self.last_data = data self.changed += 1 if self.shrinks >= self.settings.max_shrinks: raise StopShrinking() return True return False def run(self): try: self._run() except StopShrinking: pass def _run(self): self.new_buffer() mutations = 0 generation = 0 while self.last_data.status != Status.INTERESTING: if mutations >= self.settings.mutations: generation += 1 if generation >= self.settings.generations: return mutations = 0 self.new_buffer() else: self.incorporate_new_buffer(self.mutate_data_to_new_buffer()) mutations += 1 for c in range(256): if self.incorporate_new_buffer(bytes(min(c, b) for b in self.last_data.buffer)): break initial_changes = self.changed change_counter = -1 while initial_changes + self.settings.max_shrinks >= self.changed > change_counter: assert self.last_data.status == Status.INTERESTING change_counter = self.changed interval_change_counter = -1 while self.changed > interval_change_counter: interval_change_counter = self.changed i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer(self.last_data.buffer[:u] + self.last_data.buffer[v:]): i += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] self.incorporate_new_buffer( self.last_data.buffer[:u] + bytes(sorted(self.last_data.buffer[u:v])) + self.last_data.buffer[v:] ) i += 1 local_changes = -1 while local_changes < self.changed: local_changes = self.changed for c in range(1, 256): buf = self.last_data.buffer if buf.count(c) > 1: if self.incorporate_new_buffer(bytes(c - 1 if b == c else b for b in buf)): buf = self.last_data.buffer for d in range(c): if self.incorporate_new_buffer(bytes(d if b == c - 1 else b for b in buf)): break k = 8 for i in range(len(self.last_data.buffer) - k): buf = self.last_data.buffer if i + k > len(buf): break self.incorporate_new_buffer(buf[:i] + bytes(k) + buf[i + k :]) i = 0 while i < len(self.last_data.buffer): buf = self.last_data.buffer if not self.incorporate_new_buffer(buf[:i] + buf[i + 1 :]): for c in range(buf[i]): if self.incorporate_new_buffer(buf[:i] + bytes([c]) + buf[i + 1 :]): break elif self.incorporate_new_buffer(buf[:i] + bytes([c]) + self.rand_bytes((len(buf) - i - 1))): break i += 1 i = 0 while i + 1 < len(self.last_data.buffer): j = i + 1 buf = self.last_data.buffer if buf[i] > buf[j]: self.incorporate_new_buffer(buf[:i] + bytes([buf[j], buf[i]]) + buf[j + 1 :]) i += 1 if self.changed > change_counter: continue i = 0 while i < len(self.last_data.buffer): buf = self.last_data.buffer if not self.incorporate_new_buffer(buf[:i] + buf[i + 1 :]): if buf[i] == 0: buf = bytearray(buf) j = i while j >= 0: if buf[j] > 0: buf[j] -= 1 self.incorporate_new_buffer(bytes(buf)) break else: buf[j] = 255 j -= 1 i += 1 if self.changed > change_counter: continue buckets = [[] for _ in range(256)] for i, c in enumerate(self.last_data.buffer): buckets[c].append(i) indices = [] for bucket in buckets: if len(bucket) > 1: indices.extend((j, k) for j in bucket for k in bucket if j < k) for j, k in indices: buf = self.last_data.buffer if k >= len(buf): continue if buf[j] == buf[k]: c = buf[j] if c == 0: if j > 0 and buf[j - 1] > 0 and buf[k - 1] > 0: self.incorporate_new_buffer( buf[: j - 1] + bytes([buf[j - 1] - 1, 255]) + buf[j + 1 : k - 1] + bytes([buf[k - 1] - 1, 255]) + buf[k + 1 :] ) c = buf[j] if c > 0: bd = bytes([c - 1]) if self.incorporate_new_buffer(buf[:j] + bd + buf[j + 1 : k] + bd + buf[k + 1 :]): for d in range(c - 1): buf = self.last_data.buffer bd = bytes([d]) if self.incorporate_new_buffer(buf[:j] + bd + buf[j + 1 : k] + bd + buf[k + 1 :]): break if self.changed > change_counter: continue buf = self.last_data.buffer for j in range(len(buf)): buf = self.last_data.buffer if j >= len(buf): break if buf[j] == 0: continue for k in range(j + 1, len(buf)): buf = self.last_data.buffer if k >= len(buf): break if buf[j] > buf[k]: self.incorporate_new_buffer( buf[:j] + bytes([buf[k]]) + buf[j + 1 : k] + bytes([buf[j]]) + buf[k + 1 :] ) buf = self.last_data.buffer if k >= len(buf): break if buf[j] > 0 and buf[k] > 0 and buf[j] != buf[k]: if self.incorporate_new_buffer( buf[:j] + bytes([buf[j] - 1]) + buf[j + 1 : k] + bytes([buf[k] - 1]) + buf[k + 1 :] ): break if buf[j] == 0: break for t in range(256): if self.incorporate_new_buffer( buf[:j] + bytes([buf[j] - 1]) + buf[j + 1 : k] + bytes([t]) + buf[k + 1 :] ): break def mutate_data_to_new_buffer(self): n = min(len(self.last_data.buffer), self.last_data.index) if not n: return b"" if n == 1: return self.rand_bytes(1) if self.last_data.status == Status.OVERRUN: result = bytearray(self.last_data.buffer) for i, c in enumerate(self.last_data.buffer): t = self.random.randint(0, 2) if t == 0: result[i] = 0 elif t == 1: result[i] = self.random.randint(0, c) else: result[i] = c probe = self.random.randint(0, 255) if probe <= 200 or len(self.last_data.intervals) <= 1: c = self.random.randint(0, 2) i = self.random.randint(0, self.last_data.index - 1) result = bytearray(self.last_data.buffer) if c == 0: result[i] ^= 1 << self.random.randint(0, 7) elif c == 1: result[i] = 0 else: result[i] = 255 return bytes(result) else: int1 = None int2 = None while int1 == int2: i = self.random.randint(0, len(self.last_data.intervals) - 2) int1 = self.last_data.intervals[i] int2 = self.last_data.intervals[self.random.randint(i + 1, len(self.last_data.intervals) - 1)] return ( self.last_data.buffer[: int1[0]] + self.last_data.buffer[int2[0] : int2[1]] + self.last_data.buffer[int1[1] :] ) def rand_bytes(self, n): if n == 0: return b"" return self.random.getrandbits(n * 8).to_bytes(n, "big")
class TestRunner(object): def __init__( self, test_function: typing.Callable[[TestData], type(None)], settings: typing.Optional[Settings] = None, ): self._test_function = test_function self.settings = settings or Settings() self.last_data = None self.changed = 0 self.shrinks = 0 self.fill_size = min(8, self.settings.buffer_size) def new_buffer(self): buffer = os.urandom( self.fill_size) + bytes(self.settings.buffer_size - self.fill_size) self.last_data = TestData(buffer) self.test_function(self.last_data) self.last_data.freeze() self.update_fill_size() def update_fill_size(self): self.fill_size = min(max(self.fill_size, self.last_data.index * 2), self.settings.buffer_size) def test_function(self, data): try: self._test_function(data) except StopTest: pass def consider_new_test_data(self, data: TestData) -> bool: # Transition rules: # 1. Transition cannot decrease the status # 2. Any transition which increases the status is valid # 3. If the previous status was interesting, only shrinking # transitions are allowed. if self.last_data.status < data.status: return True if self.last_data.status > data.status: return False if data.status == Status.INVALID: return data.index >= self.last_data.index if data.status == Status.OVERRUN: return data.index <= self.last_data.index if data.status == Status.INTERESTING: assert len(data.buffer) <= len(self.last_data.buffer) return interest_key(data) < interest_key(self.last_data) return True def incorporate_new_buffer(self, buffer: bytes) -> bool: if buffer == self.last_data.buffer: return False data = TestData(buffer) self.test_function(data) data.freeze() if self.consider_new_test_data(data): if self.last_data.status == Status.INTERESTING: self.shrinks += 1 self.last_data = data self.update_fill_size() self.changed += 1 if self.shrinks >= self.settings.max_shrinks: raise StopShrinking() return True return False def run(self): try: self._run() except StopShrinking: pass def _run(self): self.new_buffer() mutations = 0 generation = 0 while self.last_data.status != Status.INTERESTING: if mutations >= self.settings.mutations: generation += 1 if generation >= self.settings.generations: return mutations = 0 self.incorporate_new_buffer( mutate_data_to_new_buffer(self.last_data)) else: self.new_buffer() mutations += 1 initial_changes = self.changed change_counter = -1 while (initial_changes + self.settings.max_shrinks >= self.changed > change_counter): assert self.last_data.status == Status.INTERESTING change_counter = self.changed interval_change_counter = -1 while self.changed > interval_change_counter: interval_change_counter = self.changed i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:]): i += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] self.incorporate_new_buffer( self.last_data.buffer[:u] + bytes(sorted(self.last_data.buffer[u:v])) + self.last_data.buffer[v:]) i += 1 k = 8 for i in range(len(self.last_data.buffer) - k): buf = self.last_data.buffer if i + k > len(buf): break self.incorporate_new_buffer(buf[:i] + bytes(k) + buf[i + k:]) i = 0 while i < len(self.last_data.buffer): buf = self.last_data.buffer if not self.incorporate_new_buffer(buf[:i] + buf[i + 1:]): for c in range(buf[i]): if self.incorporate_new_buffer(buf[:i] + bytes([c]) + buf[i + 1:]): break elif self.incorporate_new_buffer( buf[:i] + bytes([c]) + os.urandom(len(buf) - i - 1)): break i += 1 i = 0 while i + 1 < len(self.last_data.buffer): j = i + 1 buf = self.last_data.buffer if buf[i] > buf[j]: self.incorporate_new_buffer(buf[:i] + bytes([buf[j], buf[i]]) + buf[j + 1:]) i += 1 if self.changed > change_counter: continue i = 0 while i < len(self.last_data.buffer): buf = self.last_data.buffer if not self.incorporate_new_buffer(buf[:i] + buf[i + 1:]): if buf[i] == 0: j = i while j > 0: if buf[j] > 0: self.incorporate_new_buffer( buf[:j] + bytes([buf[j] - 1]) + bytes([255]) * (i - j) + buf[i + 1:]) break j -= 1 i += 1 if self.changed > change_counter: continue buckets = [[] for _ in range(256)] for i, c in enumerate(self.last_data.buffer): buckets[c].append(i) indices = [] for bucket in buckets: if len(bucket) > 1: indices.extend( (j, k) for j in bucket for k in bucket if j < k) for j, k in indices: buf = self.last_data.buffer if k >= len(buf): continue if buf[j] == buf[k]: c = buf[j] if c == 0: if j > 0 and buf[j - 1] > 0 and buf[k - 1] > 0: self.incorporate_new_buffer( buf[:j - 1] + bytes([buf[j - 1] - 1, 255]) + buf[j + 1:k - 1] + bytes([buf[k - 1] - 1, 255]) + buf[k + 1:]) c = buf[j] if c > 0: bd = bytes([c - 1]) if self.incorporate_new_buffer(buf[:j] + bd + buf[j + 1:k] + bd + buf[k + 1:]): for d in range(c - 1): buf = self.last_data.buffer bd = bytes([d]) self.incorporate_new_buffer(buf[:j] + bd + buf[j + 1:k] + bd + buf[k + 1:]) if self.changed > change_counter: continue buf = self.last_data.buffer for j in range(len(buf)): buf = self.last_data.buffer if j >= len(buf): break if buf[j] == 0: continue for k in range(j + 1, len(buf)): buf = self.last_data.buffer if k >= len(buf): break if buf[j] > buf[k]: self.incorporate_new_buffer(buf[:j] + bytes([buf[k]]) + buf[j + 1:k] + bytes([buf[j]]) + buf[k + 1:]) buf = self.last_data.buffer if k >= len(buf): break if buf[j] > 0 and buf[k] > 0: self.incorporate_new_buffer(buf[:j] + bytes([buf[j] - 1]) + buf[j + 1:k] + bytes([buf[k] - 1]) + buf[k + 1:])