{ "info": { "author": "Nick Iodice", "author_email": "niiodice@microsoft.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": "# Detoxed\n\n## About\n\n[![Build Status](https://travis-ci.org/Microsoft/Detoxed.svg?branch=master)](https://travis-ci.org/Microsoft/Detoxed/)\n[![codecov](https://codecov.io/gh/Microsoft/Detoxed/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/Detoxed)\n[![PyPI version](https://badge.fury.io/py/detoxed.svg)](https://badge.fury.io/py/detoxed)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/detoxed.svg)](https://pypi.org/project/detoxed/)\n[![PyPI - License](https://img.shields.io/pypi/l/detoxed.svg)](https://pypi.org/project/detoxed/)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/detoxed.svg)](https://pypi.org/project/detoxed/)\n\n`Detoxed` is an integration test harness that makes it easy to run a suite of integration tests in Python. It is easy to integrate into a CD pipeline and is currently being used to verify the correctness of Azure IoT Deployments in `Dev` and `QA` stages. It is easy to host in any CD pipelines that allow execution of user-defined scripts as part of a deployment.\n\n`Detoxed` can be used in a CD pipeline to:\n\n- Run integration test suite after a deployment to target stages\n- Run sanity (*is it up?*) tests after deploying to production stages\n- Trigger rollback of failed deployment\n\n## Installation\n\n`Detoxed` currently supports Python versions `3.4`, `3.5`, `3.6` and `3.7`. It can be installed by running `pip install detoxed`.\n\n## Samples\n\n`Detoxed` ships with a dockerized sample showing both passing and failing tests. The sample shows the following use cases:\n\n- Invoking `Detoxed` as a CLI through `bash`\n- Invoking `Detoxed` as a Python library.\n\nTo run the samples, follow these steps:\n\n```bash\n# clone the repo\ngit clone https://github.com/Microsoft/Detoxed.git\n# enter the Detoxed directory\ncd Detoxed/\n# ensure Docker is running\ndocker --version\n# build the project\ndocker build . -t detoxed\n# run the samples\ndocker run detoxed\n```\n\n## Basic Usage\n\nA common case for using `Detoxed` is to execute integration tests following the deployment of deployment either locally or to a cloud provisioned resource. Here is a simple integration test that will test if an application's HTTP endpoint is up and running:\n\n```python\n# assume module name is 'tests.integration.suite'\nimport requests\nfrom os import getenv\nfrom detoxed.integ import IntegTestBase, Fail, Pass, TestResult\n\n\nclass SimpleOutboundConnectionTestCase(IntegTestBase):\n def __init__(self):\n \"\"\"Initialize test with a name of your choice.\"\"\"\n super().__init__('Application responds to HTTP GET')\n\n # optional method to execute test setup...\n # def setup(self):\n # ...\n\n # optional method to execute test teardown...\n # def teardown(self):\n # ...\n\n def test(self) -> TestResult:\n \"\"\"Test logic here. Should return Pass() or Fail('')\"\"\"\n try:\n status_code = requests.get(getenv('APP_HTTP_ENDPOINT')).status_code\n if status_code == 200:\n return Pass()\n return Fail('status code expected to be 200 but was {}'.format(status_code))\n except Exception as ex:\n return Fail('failed to execute HTTP GET: {}'.format(ex))\n\n# More tests can live in this module or other module.\n```\n\n### Running Integ Tests Locally\n\nHere is how you would invoke this integration test locally using `Detoxed`:\n\n```bash\nexport APP_HTTP_ENDPOINT=\"...\"\nexport TEST_MODULES=\"tests.integration.suite\" # add any number of modules here...\npython3 -m detoxed $TEST_MODULES\n```\n\nThis will produce the following logs:\n\n```bash\nimporting module: tests.integration.suite\nSTARTING: 'Application responds to HTTP GET'\n\u2713 PASSED: 'Application responds to HTTP GET'\n# logs for any additional tests will be here...\n\u2713 PASSED: 'Integration Test Suite'\n```\n\n### Running Integ Tests through deployment pipeline in Azure Dev Ops\n\nHere is how you would invoke this integration test from an Azure Dev Ops Pipeline (steps for other CD technologies would be similar):\n\n- Create a variable group linked to your release stage. It should include the following variables:\n - `TEST_MODULES`: modules where your integration test classes live\n - Any test-specific environment variables. In the example test above, this would be `APP_HTTP_ENDPOINT`\n- Create a script step that runs the test harness:\n\n```bash\npython3 -m pip install detoxed\n# install other modules as needed, perhaps from requirements.txt\npython3 -m detoxed $TEST_MODULES\n```\n\n![ADO Setup](./.README.images/test_configure_ado.PNG)\n\n## Advanced Usage\n\n### Configuring Deployment Conditions\n\nThe test harness will accept an optional parameter that specifies a deployment condition. This condition defines a criteria that must be met in order for the tests to run along with a timeout that specifies how long to wait for the condition to pass. The default condition will immediately be met and will not block test execution. This behavior can be overriden by creating the test harness in python using a deployment condition that matches this interface:\n\n```python\nclass DeploymentCondition(ABC):\n \"\"\"Represents a deployment condition that should block the tests from running until met.\"\"\"\n\n @abstractmethod\n def is_met(self) -> bool:\n \"\"\"True if the condition is met, False otherwise.\"\"\"\n\n @abstractmethod\n def timeout(self) -> int:\n \"\"\"Timeout for contition to be met.\"\"\"\n```\n\n### Azure IoT Edge Deployment Condition\n\nA common case for using `Detoxed` is to execute integration tests following the deployment of an IoT application. Integration tests need to be run *after* the IoT Deployment is applied to the target devices. However the IoT Deployment is considered complete after it is applied to the IoT Hub itself. It may be quite some time until it is applied to the IoT devices. A built in deployment condition to handle this case ships with `Detoxed`.\n\n```python\nfrom detoxed.conditions import IoTDeploymentFinishedCondition\nfrom detoxed.integ import IntegTestRunner\n\n\nTEST_MODULES = [\n # your test modules here...\n]\n\ncondition = IoTDeploymentFinishedCondition(\n timeout=300, # seconds to wait for deployment to finish\n iot_hub='my-iot-hub-qa', # iot hub name\n deployment_id=102, # iot deployment ID\n device_query=\"tags.environment='qa'\") # iot device query to enumerate targeted devices\n\ntest_harness = IntegTestRunner(TEST_MODULES, condition)\npassed = test_harness.run()\n```\n\n**Notes about `IoTDeploymentFinishedCondition`**: `IoTDeploymentFinishedCondition` currently has a hard dependency on the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest) and the [Azure IoT CLI Extension](https://github.com/Azure/azure-iot-cli-extension). `az login` must be executed for `IoTDeploymentFinishedCondition` to work properly. This can be accomplished in Azure Dev Ops by running the integration test using an `Azure CLI Task` with the `Use Global Configuration` option set to `True`.\n\n## Pending development work\n\n- Enable parallel test execution\n\n## Contributing\n\nThis project welcomes contributions and suggestions. Most contributions require you to agree to a\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\nthe rights to use your contribution. For details, visit https://cla.microsoft.com.\n\nWhen you submit a pull request, a CLA-bot will automatically determine whether you need to provide\na CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions\nprovided by the bot. You will only need to do this once across all repos using our CLA.\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\ncontact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n\nFor additional guidance, refer to the project-specific [contributing guidelines](./.github/CONTRIBUTING.md) as well as the [pull request template](./.github/PULL_REQUEST_TEMPLATE.md)\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/Microsoft/Detoxed", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "detoxed", "package_url": "https://pypi.org/project/detoxed/", "platform": "", "project_url": "https://pypi.org/project/detoxed/", "project_urls": { "Homepage": "https://github.com/Microsoft/Detoxed" }, "release_url": "https://pypi.org/project/detoxed/0.1.2.3/", "requires_dist": [ "codecov (==2.0.15) ; extra == 'dev'", "flake8 (==3.7.7) ; extra == 'dev'", "isort (==4.3.10) ; extra == 'dev'", "mypy-extensions (==0.4.1) ; extra == 'dev'", "mypy (==0.670) ; extra == 'dev'", "pydocstyle (==3.0.0) ; extra == 'dev'", "pylint (==2.3.1) ; extra == 'dev'", "pytest (==4.3.1) ; extra == 'dev'", "pytest-cov (==2.6.1) ; extra == 'dev'", "setuptools (==41.0.1) ; extra == 'dev'" ], "requires_python": ">=3.2.*", "summary": "Integration test harness useful for distributed deployments", "version": "0.1.2.3" }, "last_serial": 5243892, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "19996b29d20c2a149f163f4d082f7889", "sha256": "6375b88d1c20aeb42e8474a60418a1bcafa5ee7d37ec3e647c0dbed8fcd783a9" }, "downloads": -1, "filename": "detoxed-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "19996b29d20c2a149f163f4d082f7889", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5.*", "size": 7042, "upload_time": "2019-04-25T21:58:56", "url": "https://files.pythonhosted.org/packages/56/2a/49cea67c4818a232492dedeb463c78ee8c8eefdff2536d8b69c4e9ce8cfb/detoxed-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b72c3287da6e4d17cc2ac060f747f292", "sha256": "35844c9024c31a9dfeb804ef10f67daacfd342c0bf4b297903394dcfefb57da2" }, "downloads": -1, "filename": "detoxed-0.1.0.tar.gz", "has_sig": false, "md5_digest": "b72c3287da6e4d17cc2ac060f747f292", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5.*", "size": 9178, "upload_time": "2019-04-25T21:58:59", "url": "https://files.pythonhosted.org/packages/82/cb/9e216ea53f3508cd93e2e5ccf68998c622e614bc8345157cb2e3abcb06a0/detoxed-0.1.0.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "a3538198c132c934f41fa3ba878e1e6c", "sha256": "1b6a33494362e5a3bc8bdf2f31647ba0711bf6e361bea1f73ba4f09f6680ce5b" }, "downloads": -1, "filename": "detoxed-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "a3538198c132c934f41fa3ba878e1e6c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.2.*", "size": 12443, "upload_time": "2019-05-06T23:22:03", "url": "https://files.pythonhosted.org/packages/23/07/0ee7fbd0e2ebc99d97694af9cde3b22334fb31deee8404ae614851e32d8b/detoxed-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "429bb06eab97f78a2d0a6062e001aebf", "sha256": "2b9c50f327178b284001922fa34695b959f48ce63013f31434ad4fb1e40ee517" }, "downloads": -1, "filename": "detoxed-0.1.2.tar.gz", "has_sig": false, "md5_digest": "429bb06eab97f78a2d0a6062e001aebf", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.2.*", "size": 14305, "upload_time": "2019-05-06T23:22:05", "url": "https://files.pythonhosted.org/packages/08/f8/46e2f7ce759f6d8b01cfea80f7e2bfe003588a1c6f5eaceca5e42a40f43f/detoxed-0.1.2.tar.gz" } ], "0.1.2.3": [ { "comment_text": "", "digests": { "md5": "0eba49846331930d255f1bcc238f3c64", "sha256": "aec2b2f187353fc81d586eec2e9d3a0254551dbe057bbb1d00fcf1c75fd9fe63" }, "downloads": -1, "filename": "detoxed-0.1.2.3-py3-none-any.whl", "has_sig": false, "md5_digest": "0eba49846331930d255f1bcc238f3c64", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.2.*", "size": 12468, "upload_time": "2019-05-08T17:41:34", "url": "https://files.pythonhosted.org/packages/02/29/92c509433be02d69f75a83739af649980e810e34efa3a22b1429d9aa44cf/detoxed-0.1.2.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4cc8bd7a9a385de1f454c9c12dd7e701", "sha256": "d106b71c9eee9083b56dc9b597281a01610e62105a0300a6811d62e1ace6abde" }, "downloads": -1, "filename": "detoxed-0.1.2.3.tar.gz", "has_sig": false, "md5_digest": "4cc8bd7a9a385de1f454c9c12dd7e701", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.2.*", "size": 14313, "upload_time": "2019-05-08T17:41:36", "url": "https://files.pythonhosted.org/packages/d5/fc/fd1e7d7d01d1a8ef94205fd37b5fd77f7c2c7a837063c2c0f07eca7faf7a/detoxed-0.1.2.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "0eba49846331930d255f1bcc238f3c64", "sha256": "aec2b2f187353fc81d586eec2e9d3a0254551dbe057bbb1d00fcf1c75fd9fe63" }, "downloads": -1, "filename": "detoxed-0.1.2.3-py3-none-any.whl", "has_sig": false, "md5_digest": "0eba49846331930d255f1bcc238f3c64", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.2.*", "size": 12468, "upload_time": "2019-05-08T17:41:34", "url": "https://files.pythonhosted.org/packages/02/29/92c509433be02d69f75a83739af649980e810e34efa3a22b1429d9aa44cf/detoxed-0.1.2.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4cc8bd7a9a385de1f454c9c12dd7e701", "sha256": "d106b71c9eee9083b56dc9b597281a01610e62105a0300a6811d62e1ace6abde" }, "downloads": -1, "filename": "detoxed-0.1.2.3.tar.gz", "has_sig": false, "md5_digest": "4cc8bd7a9a385de1f454c9c12dd7e701", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.2.*", "size": 14313, "upload_time": "2019-05-08T17:41:36", "url": "https://files.pythonhosted.org/packages/d5/fc/fd1e7d7d01d1a8ef94205fd37b5fd77f7c2c7a837063c2c0f07eca7faf7a/detoxed-0.1.2.3.tar.gz" } ] }