diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py index 30057e186e5f5ed6f8f2df706e18dc71a423a04d..5aa07a7380f96071ea72771cbe647e7f3a585191 100644 --- a/src/_zkapauthorizer/tests/test_storage_protocol.py +++ b/src/_zkapauthorizer/tests/test_storage_protocol.py @@ -27,6 +27,7 @@ from hypothesis import assume, given from hypothesis.strategies import data as data_strategy from hypothesis.strategies import integers, lists, sets, tuples from testtools import TestCase +from testtools.content import text_content from testtools.matchers import ( AfterPreprocessing, Always, @@ -835,40 +836,66 @@ class ShareTests(TestCase): lease_renew_secrets(), lease_cancel_secrets(), ), - test_and_write_vectors_for_shares=slot_test_and_write_vectors_for_shares(), + share_vectors=lists(slot_test_and_write_vectors_for_shares(), min_size=1), + now=posix_timestamps(), ) - def test_create_mutable( - self, storage_index, secrets, test_and_write_vectors_for_shares - ): + def test_create_mutable(self, storage_index, secrets, share_vectors, now): """ Mutable share data written using *slot_testv_and_readv_and_writev* can be read back as-written and without spending any more passes. """ - wrote, read = extract_result( - self.client.slot_testv_and_readv_and_writev( + self.clock.advance(now) + + def write(vector): + return self.client.slot_testv_and_readv_and_writev( storage_index, secrets=secrets, - tw_vectors={ - k: v.for_call() - for (k, v) in test_and_write_vectors_for_shares.items() - }, + tw_vectors={k: v.for_call() for (k, v) in vector.items()}, r_vector=[], - ), - ) - self.assertThat( - wrote, - Equals(True), - u"Server rejected a write to a new mutable slot", - ) - self.assertThat( - read, - Equals({}), - u"Server gave back read results when we asked for none.", - ) - # Now we can read it back without spending any more passes. + ) + + grant_times = {} + for n, vector in enumerate(share_vectors): + # Execute one of the write operations. It might write to multiple + # shares. + self.assertThat( + write(vector), + is_successful_write(), + ) + + # Track our progress through the list of write vectors for + # testtools failure reporting. Each call overwrites the previous + # detail so we can see how far we got, if we happen to fail + # somewhere in this loop. + self.addDetail("writev-progress", text_content("{}".format(n))) + + # Track the simulated time when each lease receives its lease. + # This scenario is constructed so that only the first write to any + # given share will result in a lease so we do not allow the grant + # time for a given share number to be updated here. Only + # sharenums being written for the first time will capture the time + # here. + grant_times.update({ + # The time is in a list to make it easier to compare the + # result with the return value of `get_lease_grant_times` + # later. The time is truncated to the integer portion because + # that is how much precision leases keep. + sharenum: [int(self.clock.seconds())] + for sharenum in vector + if sharenum not in grant_times + }) + + # Advance time so the grant times will be distinct. + self.clock.advance(1) + + # Now we can read back the last data written without spending any more + # passes. before_passes = len(self.pass_factory.issued) assert_read_back_data( - self, storage_index, secrets, test_and_write_vectors_for_shares + self, + storage_index, + secrets, + share_vectors[-1], ) after_passes = len(self.pass_factory.issued) self.assertThat( @@ -876,6 +903,15 @@ class ShareTests(TestCase): Equals(after_passes), ) + # And the lease we paid for on every share is present. + self.assertThat( + dict(get_lease_grant_times( + self.anonymous_storage_server, + storage_index, + )), + Equals(grant_times), + ) + @given( storage_index=storage_indexes(), secrets=tuples(