From 81077e6aaea0cbd2fb25580e57d74526163b1548 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 26 Sep 2020 10:00:55 +0200 Subject: [PATCH] win10 :pick: - corrects two bugs happening in Windows - modifies the build pipeline to build and run tests on Windows --- .gitattributes | 1 + CHANGELOG.md | 8 +- README.md | 9 ++ azure-pipelines.yml | 149 +++++++++++++++++++---------- blacksheep/client/cookies.py | 2 +- blacksheep/server/controllers.py | 3 +- blacksheep/server/files/dynamic.py | 3 +- itests/client_fixtures.py | 14 +-- itests/server_fixtures.py | 23 +++-- itests/test_client.py | 2 +- itests/test_server.py | 2 +- itests/utils.py | 8 ++ profiling.sh | 11 --- requirements.txt | 1 - setup.py | 3 +- tests/test_application.py | 4 +- tests/test_controllers.py | 1 - tests/test_responses.py | 6 +- 18 files changed, 156 insertions(+), 94 deletions(-) create mode 100644 .gitattributes delete mode 100755 profiling.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b3e09b..a465d3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.6] - ... + +- Corrects two bugs happening when using `blacksheep` in Windows +- Improves the test suite to be compatible with Windows +- Adds a job running in Windows to the build and validation pipeline + ## [0.2.5] - 2020-09-19 💯 - **100% test coverage**, with more than _1000_ tests @@ -38,5 +44,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improves code quality - Improves code for integration tests - Fixes bug [#37](https://github.com/RobertoPrevato/BlackSheep/issues/37) - - diff --git a/README.md b/README.md index f8f9d5dd..632d1728 100644 --- a/README.md +++ b/README.md @@ -190,5 +190,14 @@ loop.run_until_complete(client_example(loop)) ``` +## Supported platforms and runtimes +The following Python versions are supported and tested by [validation pipeline](./azure-pipelines.yml): +* Python 3.7 (cpython) +* Python 3.8 (cpython) + +The following platforms are supported and tested by [validation pipeline](./azure-pipelines.yml): +* Ubuntu 18.04 +* Windows 10 + ## Documentation Please refer to the [project Wiki](https://github.com/RobertoPrevato/BlackSheep/wiki). diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5e7137a7..5e844ad2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,52 +1,99 @@ -# BlackSheep build pipeline - trigger: -- master - -pool: - vmImage: 'ubuntu-latest' -strategy: - matrix: - Python37: - python.version: '3.7' - Python38: - python.version: '3.8' - -steps: -- task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - displayName: 'Use Python $(python.version)' - -- bash: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pytest pytest-cov pytest-azurepipelines - displayName: 'Install dependencies' - -- bash: | - make compile - displayName: 'Compile Cython extensions' - -- bash: | - pytest --doctest-modules --junitxml=junit/test-results.xml --cov-report html --cov=blacksheep - displayName: 'tests' - -- bash: | - if [ -f "build_info.txt" ]; then rm build_info.txt; fi - echo "Build id: $(Build.BuildId)" >> build_info.txt - echo "Build date: $(date)" >> build_info.txt - echo "Build URL: https://dev.azure.com/robertoprevato/BlackSheep/_build/results?buildId=$(Build.BuildId)&view=results" >> build_info.txt - echo "Build version: $(Build.DefinitionVersion)" >> build_info.txt - displayName: 'writes build id' - -- bash: | - python setup.py sdist - displayName: 'create artifacts' - -- task: PublishBuildArtifacts@1 - displayName: 'publish dist artifacts' - condition: and(succeeded(), eq(variables['python.version'], '3.8')) - inputs: - PathtoPublish: dist - ArtifactName: dist + - master + +jobs: + - job: Linux + pool: + vmImage: "ubuntu-latest" + strategy: + matrix: + Python37: + python.version: "3.7" + Python38: + python.version: "3.8" + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: "$(python.version)" + displayName: "Use Python $(python.version)" + + - bash: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install uvloop==0.14.0 + pip install pytest pytest-cov pytest-azurepipelines + displayName: "Install dependencies" + + - bash: | + make compile + displayName: "Compile Cython extensions" + + - bash: | + pytest --doctest-modules --junitxml=junit/test-results.xml --cov-report html --cov=blacksheep + displayName: "tests" + + - script: | + flake8 blacksheep + flake8 tests + displayName: "flake8 tests" + + - bash: | + if [ -f "build_info.txt" ]; then rm build_info.txt; fi + echo "Build id: $(Build.BuildId)" >> build_info.txt + echo "Build date: $(date)" >> build_info.txt + echo "Build URL: https://dev.azure.com/robertoprevato/BlackSheep/_build/results?buildId=$(Build.BuildId)&view=results" >> build_info.txt + echo "Build version: $(Build.DefinitionVersion)" >> build_info.txt + displayName: "writes build id" + + - bash: | + python setup.py sdist + displayName: "create artifacts" + + - task: PublishBuildArtifacts@1 + displayName: "publish dist artifacts" + condition: and(succeeded(), eq(variables['python.version'], '3.8')) + inputs: + PathtoPublish: dist + ArtifactName: dist + - job: Windows + pool: + vmImage: "windows-latest" + strategy: + matrix: + Python37: + python.version: "3.7" + Python38: + python.version: "3.8" + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: "$(python.version)" + displayName: "Use Python $(python.version)" + + - script: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-azurepipelines + displayName: "Install dependencies" + + - script: | + cython blacksheep/url.pyx + cython blacksheep/exceptions.pyx + cython blacksheep/headers.pyx + cython blacksheep/cookies.pyx + cython blacksheep/contents.pyx + cython blacksheep/messages.pyx + cython blacksheep/scribe.pyx + cython blacksheep/baseapp.pyx + + python setup.py build_ext --inplace + displayName: "Compile Cython extensions" + + - script: | + flake8 blacksheep + flake8 tests + displayName: "flake8 tests" + + - script: | + pytest --doctest-modules --junitxml=junit/test-results.xml + displayName: "tests" diff --git a/blacksheep/client/cookies.py b/blacksheep/client/cookies.py index 295cf18e..49853f32 100644 --- a/blacksheep/client/cookies.py +++ b/blacksheep/client/cookies.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta from ipaddress import ip_address -from typing import Any, Optional, Dict, Iterable, TypeVar +from typing import Optional, Dict, Iterable, TypeVar from blacksheep import URL, Cookie diff --git a/blacksheep/server/controllers.py b/blacksheep/server/controllers.py index 9883b975..1cf32923 100644 --- a/blacksheep/server/controllers.py +++ b/blacksheep/server/controllers.py @@ -1,6 +1,5 @@ import sys -from types import FrameType -from typing import Any, Callable, Optional +from typing import Any, Optional from blacksheep import Request, Response from blacksheep.server.responses import ( diff --git a/blacksheep/server/files/dynamic.py b/blacksheep/server/files/dynamic.py index d3034d96..64fc10f6 100644 --- a/blacksheep/server/files/dynamic.py +++ b/blacksheep/server/files/dynamic.py @@ -1,3 +1,4 @@ +from blacksheep.utils import join_fragments from blacksheep.server.authorization import allow_anonymous import html import os @@ -75,7 +76,7 @@ def get_files_list_html_response( rel_path = item.get("rel_path") assert rel_path is not None full_rel_path = html.escape( - os.path.join(root_path, parent_folder_path, rel_path) + join_fragments(root_path, parent_folder_path, rel_path) ) info_lines.append(f'
  • {rel_path}
  • ') info = "".join(info_lines) diff --git a/itests/client_fixtures.py b/itests/client_fixtures.py index abc7e095..12b13546 100644 --- a/itests/client_fixtures.py +++ b/itests/client_fixtures.py @@ -1,3 +1,4 @@ +from itests.utils import get_sleep_time from blacksheep.client.pool import ClientConnectionPools import os import pathlib @@ -27,7 +28,7 @@ def event_loop(): @pytest.fixture(scope="module") def server_host(): - return "0.0.0.0" + return "127.0.0.1" @pytest.fixture(scope="module") @@ -58,15 +59,16 @@ def session_alt(event_loop): event_loop.run_until_complete(session.close()) +def start_server(): + print(f"[*] Flask app listening on 0.0.0.0:44777") + app.run(host="127.0.0.1", port=44777) + + @pytest.fixture(scope="module", autouse=True) def server(server_host, server_port): - def start_server(): - print(f"[*] Flask app listening on {server_host}:{server_port}") - app.run(host=server_host, port=server_port) - server_process = Process(target=start_server) server_process.start() - sleep(0.5) + sleep(get_sleep_time()) yield 1 diff --git a/itests/server_fixtures.py b/itests/server_fixtures.py index a4e2d751..97795aa3 100644 --- a/itests/server_fixtures.py +++ b/itests/server_fixtures.py @@ -1,3 +1,4 @@ +import os import socket from multiprocessing import Process from time import sleep @@ -7,7 +8,7 @@ from .app import app from .app_two import app_two -from .utils import ClientSession +from .utils import ClientSession, get_sleep_time @pytest.fixture(scope="module") @@ -44,14 +45,19 @@ def session_two(server_host, server_port_two): return ClientSession(f"http://{server_host}:{server_port_two}") +def start_server(): + uvicorn.run(app, host="127.0.0.1", port=44555, log_level="debug") + + +def start_server2(): + uvicorn.run(app_two, host="127.0.0.1", port=44556, log_level="debug") + + @pytest.fixture(scope="module", autouse=True) def server(server_host, server_port): - def start_server(): - uvicorn.run(app, host=server_host, port=server_port, log_level="debug") - server_process = Process(target=start_server) server_process.start() - sleep(0.5) + sleep(get_sleep_time()) yield 1 @@ -61,12 +67,9 @@ def start_server(): @pytest.fixture(scope="module", autouse=True) def server_two(server_host, server_port_two): - def start_server(): - uvicorn.run(app_two, host=server_host, port=server_port_two, log_level="debug") - - server_process = Process(target=start_server) + server_process = Process(target=start_server2) server_process.start() - sleep(0.5) + sleep(1.5) yield 1 diff --git a/itests/test_client.py b/itests/test_client.py index 31bcafb6..8ae7e42c 100644 --- a/itests/test_client.py +++ b/itests/test_client.py @@ -19,7 +19,7 @@ def ensure_success(response: Response): @pytest.mark.asyncio async def test_get_plain_text(session, event_loop): - for i in range(5): + for _ in range(5): response = await session.get("/hello-world") ensure_success(response) text = await response.text() diff --git a/itests/test_server.py b/itests/test_server.py index cfddb532..ee750c21 100644 --- a/itests/test_server.py +++ b/itests/test_server.py @@ -198,7 +198,7 @@ def test_exception_handling_with_details(session): assert response.status_code == 500 details = response.text - assert "itests/app.py" in details + assert "app.py" in details assert "itests.utils.CrashTest: Crash Test!" in details diff --git a/itests/utils.py b/itests/utils.py index c16afb8c..de15c9c5 100644 --- a/itests/utils.py +++ b/itests/utils.py @@ -62,3 +62,11 @@ def assert_file_content_equals(file_path, content): def get_file_bytes(file_path): with open(file_path, mode="rb") as file: return file.read() + + +def get_sleep_time(): + # when starting a server process, + # a longer sleep time is necessary on Windows + if os.name == "nt": + return 1.5 + return 0.5 diff --git a/profiling.sh b/profiling.sh deleted file mode 100755 index 0a93776e..00000000 --- a/profiling.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -if [ $1 -eq 1 ] -then -echo enable -find ./blacksheep/*.pxd -maxdepth 1 -type f -exec sed -i ' 1 s/.*/&, profile=True/' {} \; -find ./blacksheep/*.pyx -maxdepth 1 -type f -exec sed -i '1s;^;# cython: profile=True\n;' {} \; -else -echo disable -find ./blacksheep/*.pyx -maxdepth 1 -type f -exec sed -i '/# cython: profile=True/d' {} \; -find ./blacksheep/*.pxd -maxdepth 1 -type f -exec sed -i 's/, profile=True//g' {} \; -fi \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4945276a..e8ffda02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,7 +45,6 @@ typed-ast==1.4.1 typing-extensions==3.7.4.1 urllib3==1.25.7 uvicorn==0.11.8 -uvloop==0.14.0 wcwidth==0.1.7 websockets==8.1 Werkzeug==0.16.0 diff --git a/setup.py b/setup.py index e6059960..915d28fb 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def readme(): setup( name="blacksheep", - version="0.2.5", + version="0.2.6", description="Fast web framework and HTTP client for Python asyncio", long_description=readme(), long_description_content_type="text/markdown", @@ -19,6 +19,7 @@ def readme(): "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Operating System :: OS Independent", "Framework :: AsyncIO", ], diff --git a/tests/test_application.py b/tests/test_application.py index 79229997..2b30cbe2 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -2041,7 +2041,7 @@ async def home(request, x_foo: FromHeader[List[UUID]]): await app( get_example_scope( "GET", - f"/", + "/", [(b"x_foo", str(value_1).encode()), (b"x_foo", str(value_2).encode())], ), MockReceive(), @@ -2494,7 +2494,7 @@ async def home(): await app.start() await app( - get_example_scope("GET", f"/", []), + get_example_scope("GET", "/", []), MockReceive(), MockSend(), ) diff --git a/tests/test_controllers.py b/tests/test_controllers.py index bb1d9195..8b839813 100644 --- a/tests/test_controllers.py +++ b/tests/test_controllers.py @@ -1,4 +1,3 @@ -from abc import abstractmethod from blacksheep.server.application import RequiresServiceContainerError import pytest from typing import Optional diff --git a/tests/test_responses.py b/tests/test_responses.py index 09d2f3be..a10cf96c 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -65,15 +65,15 @@ STATUS_METHODS_NO_BODY = [(no_content, 204), (not_modified, 304)] -EXAMPLE_HTML = f""" +EXAMPLE_HTML = """ Example