From aa5eae842444ed380ca39a638278cb7d56cc9c40 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Sun, 18 Jul 2021 21:44:49 +0100 Subject: [PATCH] Start introducing the framework for a test suite --- .gitignore | 4 +- testsuite/README.md | 18 +++ testsuite/datmantests/__init__.py | 0 .../datmantests/test_backup_and_extract.py | 6 + testsuite/helpers/__init__.py | 104 ++++++++++++++ testsuite/setup.py | 130 ++++++++++++++++++ testsuite/yamatests/__init__.py | 0 .../yamatests/test_store_and_retrieve.py | 6 + 8 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 testsuite/README.md create mode 100644 testsuite/datmantests/__init__.py create mode 100644 testsuite/datmantests/test_backup_and_extract.py create mode 100644 testsuite/helpers/__init__.py create mode 100644 testsuite/setup.py create mode 100644 testsuite/yamatests/__init__.py create mode 100644 testsuite/yamatests/test_store_and_retrieve.py diff --git a/.gitignore b/.gitignore index 84113ae..30f5039 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ /yama.iml /.project /.gdb_history - - +/testsuite/.idea +/testsuite/venv diff --git a/testsuite/README.md b/testsuite/README.md new file mode 100644 index 0000000..4d9ceef --- /dev/null +++ b/testsuite/README.md @@ -0,0 +1,18 @@ +# yama and datman test suite + +To run the tests in this suite, you shall require Python 3.8+. + +In a virtual environment: + +`pip install .` + +`` + + + +## Development instructions + +`pip install -e .[dev]` + + +To run linter checks, run `./scripts-dev/lint.sh`. diff --git a/testsuite/datmantests/__init__.py b/testsuite/datmantests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testsuite/datmantests/test_backup_and_extract.py b/testsuite/datmantests/test_backup_and_extract.py new file mode 100644 index 0000000..577a64a --- /dev/null +++ b/testsuite/datmantests/test_backup_and_extract.py @@ -0,0 +1,6 @@ +from unittest import TestCase + + +class TestBackupAndExtract(TestCase): + def test_backup_one_full(self): + pass \ No newline at end of file diff --git a/testsuite/helpers/__init__.py b/testsuite/helpers/__init__.py new file mode 100644 index 0000000..46820b0 --- /dev/null +++ b/testsuite/helpers/__init__.py @@ -0,0 +1,104 @@ +import os.path +from hashlib import sha256 +from pathlib import Path +from random import Random +from typing import Union, Tuple + +import attr +from immutabledict import immutabledict + +MIN_FILE_SIZE = 0 +MAX_FILE_SIZE = 64 * 1024 * 1024 + +CHUNK_SIZE = 4096 + +ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789" + + +@attr.s(auto_attribs=True, frozen=True) +class FileDescriptor: + sha256_sum: str + mtime_ms: int + permissions: int + owner: int + group: int + +@attr.s(auto_attribs=True, frozen=True) +class DirectoryDescriptor: + contents: immutabledict[str, Union[FileDescriptor, "DirectoryDescriptor"]] + mtime_ms: int + permissions: int + owner: int + group: int + + +def generate_random_file(rng: Random, path: Path) -> FileDescriptor: + """ + Generates a random file at the given path, and returns its descriptor. + :param rng: PRNG to use + :param path: path to use + :return: sha256 hex string + """ + file_size = rng.randint(MIN_FILE_SIZE, MAX_FILE_SIZE) + + bytes_to_gen = file_size + + sha256_hasher = sha256() + + with path.open('wb') as file: + while bytes_to_gen > CHUNK_SIZE: + next_bytes = rng.randbytes(CHUNK_SIZE) + file.write(next_bytes) + sha256_hasher.update(next_bytes) + bytes_to_gen -= CHUNK_SIZE + next_bytes = rng.randbytes(bytes_to_gen) + file.write(next_bytes) + sha256_hasher.update(next_bytes) + + final_sha256 = sha256_hasher.hexdigest() + file_stat = os.stat(path) + + return FileDescriptor( + final_sha256, + file_stat.st_mtime_ns // 1000000, + file_stat.st_mode, + file_stat.st_uid, + file_stat.st_gid + ) + + +def generate_random_dir(rng: Random, path: Path, max_remaining_files: int) -> Tuple[DirectoryDescriptor, int]: + """ + Generates a random directory at the given path, and returns its descriptor (and the remaining number of files allowed). + :param rng: PRNG to use + :param path: path to use + :param max_remaining_files: The maximum number of files allowed. + :return: (descriptor, number of files allowed remaining) + """ + + os.mkdir(path) + + num_files = rng.randint(0, max_remaining_files) + max_remaining_files -= num_files + + contents = dict() + + for _ in range(num_files): + filename_len = rng.randint(4, 16) + filename = "".join(rng.choice(ALPHABET) for _ in range(filename_len)) + filepath = path.joinpath(filename) + is_file = rng.choice((True, False)) + if is_file: + contents[filename] = generate_random_file(rng, filepath) + else: + contents[filename], max_remaining_files = generate_random_dir(rng, filepath, max_remaining_files) + + file_stat = os.stat(path) + + return DirectoryDescriptor( + immutabledict(contents), + file_stat.st_mtime_ns // 1000000, + file_stat.st_mode, + file_stat.st_uid, + file_stat.st_gid + ), max_remaining_files diff --git a/testsuite/setup.py b/testsuite/setup.py new file mode 100644 index 0000000..3edf69e --- /dev/null +++ b/testsuite/setup.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import io +import os +import sys +from shutil import rmtree + +from setuptools import find_packages, setup, Command + +# Package meta-data. +NAME = "yamadatmantestsuite" +DESCRIPTION = "test suite for yama and datman" +URL = "https://bics.ga/reivilibre/yama" +EMAIL = "reivi@librepush.net" +AUTHOR = "Olivier 'reivilibre'" +REQUIRES_PYTHON = ">=3.8.0" +VERSION = "0.0.0" + +# What packages are required for this module to be executed? +REQUIRED = [ + "green", + "attrs", + "immutabledict" +] + + +# What packages are optional? +EXTRAS = { + "dev": [ + "black==21.7b0", + "flake8==3.9.2", + "isort==5.9.2" + ] +} + +# The rest you shouldn't have to touch too much :) +# ------------------------------------------------ +# Except, perhaps the License and Trove Classifiers! +# If you do change the License, remember to change the Trove Classifier for that! + +here = os.path.abspath(os.path.dirname(__file__)) + +# Import the README and use it as the long-description. +# Note: this will only work if 'README.md' is present in your MANIFEST.in file! +try: + with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f: + long_description = "\n" + f.read() +except FileNotFoundError: + long_description = DESCRIPTION + +# Load the package's __version__.py module as a dictionary. +about = {} +if not VERSION: + project_slug = NAME.lower().replace("-", "_").replace(" ", "_") + with open(os.path.join(here, project_slug, "__version__.py")) as f: + exec(f.read(), about) +else: + about["__version__"] = VERSION + + +class UploadCommand(Command): + """Support setup.py upload.""" + + description = "Build and publish the package." + user_options = [] + + @staticmethod + def status(s): + """Prints things in bold.""" + print("\033[1m{0}\033[0m".format(s)) + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + try: + self.status("Removing previous builds…") + rmtree(os.path.join(here, "dist")) + except OSError: + pass + + self.status("Building Source and Wheel (universal) distribution…") + os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable)) + + self.status("Uploading the package to PyPI via Twine…") + os.system("twine upload dist/*") + + self.status("Pushing git tags…") + os.system("git tag v{0}".format(about["__version__"])) + os.system("git push --tags") + + sys.exit() + + +# Where the magic happens: +setup( + name=NAME, + version=about["__version__"], + description=DESCRIPTION, + long_description=long_description, + long_description_content_type="text/markdown", + author=AUTHOR, + author_email=EMAIL, + python_requires=REQUIRES_PYTHON, + url=URL, + packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), + # If your package is a single module, use this instead of 'packages': + # py_modules=['mypackage'], + entry_points={ + "console_scripts": [ + "phototrie=phototrie.phototrie:cli", + "batchrename=phototrie.batchrename:cli", + "datename=phototrie.datename:cli", + ], + }, + install_requires=REQUIRED, + extras_require=EXTRAS, + include_package_data=True, + # TODO license='GPL3', + classifiers=[ + # Trove classifiers + # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers + "Programming Language :: Python", + "Programming Language :: Python :: 3", + ], +) diff --git a/testsuite/yamatests/__init__.py b/testsuite/yamatests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testsuite/yamatests/test_store_and_retrieve.py b/testsuite/yamatests/test_store_and_retrieve.py new file mode 100644 index 0000000..7155edc --- /dev/null +++ b/testsuite/yamatests/test_store_and_retrieve.py @@ -0,0 +1,6 @@ +from unittest import TestCase + + +# class TestStoreAndRetrieve(TestCase): +# def test_store_and_retrieve(self): +# \ No newline at end of file