Skip to content
Snippets Groups Projects
strategies.py 8.93 KiB
Newer Older
  • Learn to ignore specific revisions
  • # Copyright 2019 PrivateStorage.io, LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    """
    Hypothesis strategies for property testing.
    """
    
    
    from base64 import (
        urlsafe_b64encode,
    )
    
    
    from hypothesis.strategies import (
    
        lists,
        tuples,
        dictionaries,
    
    from twisted.web.test.requesthelper import (
        DummyRequest,
    )
    
    
    from allmydata.interfaces import (
        StorageIndex,
        LeaseRenewSecret,
        LeaseCancelSecret,
    
        WriteEnablerSecret,
    
    from allmydata.client import (
        config_from_string,
    )
    
    
    from ..model import (
        Pass,
    
    
    def _merge_dictionaries(dictionaries):
        result = {}
        for d in dictionaries:
            result.update(d)
        return result
    
    
    def _tahoe_config_quote(text):
        return text.replace(u"%", u"%%")
    
    
    def _config_string_from_sections(divided_sections):
        sections = _merge_dictionaries(divided_sections)
        return u"".join(list(
            u"[{name}]\n{items}\n".format(
                name=name,
                items=u"\n".join(
                    u"{key} = {value}".format(key=key, value=_tahoe_config_quote(value))
                    for (key, value)
                    in contents.items()
                )
            )
            for (name, contents) in sections.items()
        ))
    
    
    def tahoe_config_texts(storage_client_plugins):
        """
        Build the text of complete Tahoe-LAFS configurations for a node.
        """
        return builds(
            lambda *sections: _config_string_from_sections(
                sections,
            ),
            fixed_dictionaries(
                {
                    "storageclient.plugins.{}".format(name): configs
                    for (name, configs)
                    in storage_client_plugins.items()
                },
            ),
            fixed_dictionaries(
                {
                    "node": fixed_dictionaries(
                        {
                            "nickname": node_nicknames(),
                        },
                    ),
                    "client": fixed_dictionaries(
                        {
                            "storage.plugins": just(
                                u",".join(storage_client_plugins.keys()),
                            ),
                        },
                    ),
                },
            ),
        )
    
    
    def tahoe_configs(storage_client_plugins=None):
        """
        Build complete Tahoe-LAFS configurations for a node.
        """
        if storage_client_plugins is None:
            storage_client_plugins = {}
        return tahoe_config_texts(
            storage_client_plugins,
        ).map(
            lambda config_text: lambda basedir, portnumfile: config_from_string(
                basedir,
                portnumfile,
                config_text.encode("utf-8"),
            ),
        )
    
    def node_nicknames():
        """
        Builds Tahoe-LAFS node nicknames.
        """
        return text(
            min_size=0,
            max_size=16,
            alphabet=characters(
                blacklist_categories={
                    # Surrogates
                    u"Cs",
                    # Unnamed and control characters
                    u"Cc",
                },
            ),
        )
    
    
    
    def configurations():
        """
    
        Build configuration values for the server-side plugin.
    
        """
        return just({})
    
    def client_configurations():
        """
        Build configuration values for the client-side plugin.
        """
        return just({})
    
    
    
        Build unicode strings in the format of vouchers.
    
        """
        return binary(
            min_size=32,
            max_size=32,
        ).map(
            urlsafe_b64encode,
    
            lambda voucher: voucher.decode("ascii"),
    
    def random_tokens():
        """
        Build random tokens as unicode strings.
        """
        return binary(
            min_size=32,
            max_size=32,
        ).map(
            urlsafe_b64encode,
        ).map(
            lambda token: RandomToken(token.decode("ascii")),
        )
    
    
    
    def zkaps():
        """
        Build random ZKAPs as ``Pass` instances.
        """
        return binary(
            min_size=32,
            max_size=32,
        ).map(
            urlsafe_b64encode,
        ).map(
            lambda zkap: Pass(zkap.decode("ascii")),
        )
    
    
    
    def request_paths():
        """
        Build lists of unicode strings that represent the path component of an
        HTTP request.
    
        :see: ``requests``
        """
    
    
    def requests(paths=request_paths()):
        """
        Build objects providing ``twisted.web.iweb.IRequest``.
        """
        return builds(
            DummyRequest,
            paths,
        )
    
    
    
    def storage_indexes():
        """
        Build Tahoe-LAFS storage indexes.
        """
        return binary(
            min_size=StorageIndex.minLength,
            max_size=StorageIndex.maxLength,
        )
    
    
    def lease_renew_secrets():
        """
        Build Tahoe-LAFS lease renewal secrets.
        """
        return binary(
            min_size=LeaseRenewSecret.minLength,
            max_size=LeaseRenewSecret.maxLength,
        )
    
    
    def lease_cancel_secrets():
        """
        Build Tahoe-LAFS lease cancellation secrets.
        """
        return binary(
            min_size=LeaseCancelSecret.minLength,
            max_size=LeaseCancelSecret.maxLength,
        )
    
    
    
    def write_enabler_secrets():
        """
        Build Tahoe-LAFS write enabler secrets.
        """
        return binary(
            min_size=WriteEnablerSecret.minLength,
            max_size=WriteEnablerSecret.maxLength,
        )
    
    
    
    def sharenums():
        """
        Build Tahoe-LAFS share numbers.
        """
        return integers(
            min_value=0,
            max_value=255,
        )
    
    
    def sharenum_sets():
        """
        Build sets of Tahoe-LAFS share numbers.
        """
        return sets(
            sharenums(),
            min_size=1,
            max_size=255,
        )
    
    
    def sizes():
        """
        Build Tahoe-LAFS share sizes.
        """
    
        return integers(
            # Size 0 data isn't data, it's nothing.
            min_value=1,
            # Just for practical purposes...
            max_value=2 ** 16,
        )
    
    
    def offsets():
        """
        Build Tahoe-LAFS share offsets.
        """
    
        return integers(
            min_value=0,
            # Just for practical purposes...
            max_value=2 ** 16,
        )
    
    
    
    def bytes_for_share(sharenum, size):
        """
        :return bytes: marginally distinctive bytes of a certain length for the
            given share number
        """
        if 0 <= sharenum <= 255:
            return (unichr(sharenum) * size).encode("latin-1")
        raise ValueError("Sharenum must be between 0 and 255 inclusive.")
    
    
    def shares():
        """
        Build Tahoe-LAFS share data.
        """
        return tuples(
            sharenums(),
            sizes()
        ).map(
    
            lambda num_and_size: bytes_for_share(*num_and_size),
    
        )
    
    
    def data_vectors():
        """
        Build Tahoe-LAFS data vectors.
        """
        return lists(
            tuples(
                offsets(),
                shares(),
            ),
            # An empty data vector doesn't make much sense.  If you have no data
            # to write, you should probably use slot_readv instead.  Also,
            # Tahoe-LAFS explodes if you pass an empty data vector -
            # storage/server.py, OSError(ENOENT) from `os.listdir(bucketdir)`.
            min_size=1,
            # Just for practical purposes...
            max_size=8,
        )
    
    
    def test_vectors():
        """
        Build Tahoe-LAFS test vectors.
        """
        return lists(
            # XXX TODO
            just(None),
            min_size=0,
            max_size=0,
        )
    
    
    
    @attr.s(frozen=True)
    class TestAndWriteVectors(object):
        """
        Provide an alternate structure for the values required by the
        ``tw_vectors`` parameter accepted by
        ``RIStorageServer.slot_testv_and_readv_and_writev``.
        """
        test_vector = attr.ib()
        write_vector = attr.ib()
        new_length = attr.ib()
    
        def for_call(self):
            """
            Construct a value suitable to be passed as ``tw_vectors`` to
            ``slot_testv_and_readv_and_writev``.
            """
            return (self.test_vector, self.write_vector, self.new_length)
    
    
    
    def test_and_write_vectors():
        """
        Build Tahoe-LAFS test and write vectors for a single share.
        """
    
        return builds(
            TestAndWriteVectors,
    
            test_vectors(),
            data_vectors(),
            one_of(
                just(None),
                sizes(),
            ),
        )
    
    
    def test_and_write_vectors_for_shares():
        """
        Build Tahoe-LAFS test and write vectors for a number of shares.
        """
        return dictionaries(
            sharenums(),
            test_and_write_vectors(),
            # An empty dictionary wouldn't make much sense.  And it provokes a
            # NameError from Tahoe, storage/server.py:479, `new_length` referenced
            # before assignment.
            min_size=1,
            # Just for practical purposes...
    
        Build announcements for the ZKAPAuthorizer plugin.
    
        """
        return just({})