Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] add testserver #19153

Merged
merged 31 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6dff5d5
tests passing locally
iscai-msft Jun 8, 2021
58c184a
move pytests.init to root
iscai-msft Jun 8, 2021
c7663fe
remove set formatting from windows run
iscai-msft Jun 8, 2021
bec31ed
try to fix windows testserver start
iscai-msft Jun 8, 2021
405d5e8
uncomment testserver termination
iscai-msft Jun 8, 2021
fb692df
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-pytho…
iscai-msft Jun 17, 2021
f826377
add devops scripts to start testserver
iscai-msft Jun 17, 2021
6ceb795
scriptSource -> scriptPath
iscai-msft Jun 17, 2021
0f10e5e
set env vars
iscai-msft Jun 18, 2021
c35fdc2
add pwsh testserver
iscai-msft Jun 18, 2021
f8fd247
return result
iscai-msft Jun 21, 2021
c4fd6d2
try retuning exit code
iscai-msft Jun 21, 2021
b322e28
remove ci work, make pytest fixture module level
iscai-msft Jun 21, 2021
449f42a
tests working without pytest.ini
iscai-msft Jun 21, 2021
a021de2
only have testserver fixture in conftest
iscai-msft Jun 21, 2021
7a68dd3
switch to package scope
iscai-msft Jun 21, 2021
df42f15
unite testserver setting
iscai-msft Jun 21, 2021
da62f4b
switch to environment variables
iscai-msft Jun 22, 2021
e06ebb4
see what happens if we don't kill testserver
iscai-msft Jun 22, 2021
04ddbd0
cycle through ports
iscai-msft Jun 22, 2021
e860e8a
remove scripts
iscai-msft Jun 22, 2021
2f39ad2
allow 2.7 compatibility
iscai-msft Jun 22, 2021
f503b2c
wait longer for pypy
iscai-msft Jun 23, 2021
8ccce34
increase sleep to 2 for pypy
iscai-msft Jun 23, 2021
30c6e39
move core testserver into tests
iscai-msft Jun 24, 2021
20e593a
switch to urllib requesting to see if port open
iscai-msft Jun 24, 2021
1483f8c
ignore coretestserver readme
iscai-msft Jun 24, 2021
4cbf6a8
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
iscai-msft Jun 24, 2021
0f2ae93
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
iscai-msft Jun 24, 2021
3d12735
add readme rst
iscai-msft Jun 25, 2021
c4ac95b
ignore readme.rst
iscai-msft Jun 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/.docsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ known_content_issues:
- ['sdk/containerregistry/azure-containerregistry/swagger/README.md', '#4554']
- ['sdk/appconfiguration/azure-appconfiguration/swagger/README.md', '#4554']
- ['sdk/attestation/azure-security-attestation/swagger/README.md', '#4554']
- ['sdk/core/azure-core/tests/testserver_tests/coretestserver/README.rst', '#4554']

# common.
- ['sdk/appconfiguration/azure-appconfiguration/README.md', 'common']
Expand Down
1 change: 1 addition & 0 deletions sdk/core/azure-core/dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ opencensus-ext-threading
mock; python_version < '3.3'
-e ../../../tools/azure-sdk-tools
-e ../../../tools/azure-devtools
-e tests/testserver_tests/coretestserver
88 changes: 88 additions & 0 deletions sdk/core/azure-core/tests/testserver_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# --------------------------------------------------------------------------
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# The MIT License (MIT)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the ""Software""), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# --------------------------------------------------------------------------
import time
import pytest
import signal
import os
import subprocess
import sys
import random
from six.moves import urllib

def is_port_available(port_num):
req = urllib.request.Request("http://localhost:{}/health".format(port_num))
try:
return urllib.request.urlopen(req).code != 200
except Exception as e:
return True

def get_port():
count = 3
for _ in range(count):
port_num = random.randrange(3000, 5000)
if is_port_available(port_num):
return port_num
raise TypeError("Tried {} times, can't find an open port".format(count))

@pytest.fixture
def port():
return os.environ["FLASK_PORT"]

def start_testserver():
port = get_port()
os.environ["FLASK_APP"] = "coretestserver"
os.environ["FLASK_PORT"] = str(port)
cmd = "flask run -p {}".format(port)
if os.name == 'nt': #On windows, subprocess creation works without being in the shell
child_process = subprocess.Popen(cmd, env=dict(os.environ))
else:
#On linux, have to set shell=True
child_process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid, env=dict(os.environ))
count = 5
for _ in range(count):
if not is_port_available(port):
return child_process
time.sleep(1)
raise ValueError("Didn't start!")

def terminate_testserver(process):
if os.name == 'nt':
process.kill()
else:
os.killpg(os.getpgid(process.pid), signal.SIGTERM) # Send the signal to all the process groups

@pytest.fixture(autouse=True, scope="package")
def testserver():
"""Start the Autorest testserver."""
server = start_testserver()
yield
terminate_testserver(server)


# Ignore collection of async tests for Python 2
collect_ignore_glob = []
if sys.version_info < (3, 5):
collect_ignore_glob.append("*_async.py")
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Testserver for Python Azure Core
==============================================

This package contains a testserver to aid in testing of Python's Azure Core package

It has the following component:

coretestserver
--------------

A testing server for Azure Core tests to use

Contributing
============

This project has adopted the
`Microsoft Open Source Code of Conduct <https://opensource.microsoft.com/codeofconduct/>`__.
For more information see the
`Code of Conduct FAQ <https://opensource.microsoft.com/codeofconduct/faq/>`__
or contact
`opencode@microsoft.com <mailto:opencode@microsoft.com>`__
with any additional questions or comments.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# coding: utf-8
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------

from flask import Flask, Response
from .test_routes import (
basic_api,
encoding_api,
errors_api,
streams_api,
urlencoded_api,
multipart_api,
xml_api
)

app = Flask(__name__)
app.register_blueprint(basic_api, url_prefix="/basic")
app.register_blueprint(encoding_api, url_prefix="/encoding")
app.register_blueprint(errors_api, url_prefix="/errors")
app.register_blueprint(streams_api, url_prefix="/streams")
app.register_blueprint(urlencoded_api, url_prefix="/urlencoded")
app.register_blueprint(multipart_api, url_prefix="/multipart")
app.register_blueprint(xml_api, url_prefix="/xml")

@app.route('/health', methods=['GET'])
def latin_1_charset_utf8():
return Response(status=200)

if __name__ == "__main__":
app.run(debug=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# coding: utf-8
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------

from .basic import basic_api
from .encoding import encoding_api
from .errors import errors_api
from .multipart import multipart_api
from .streams import streams_api
from .urlencoded import urlencoded_api
from .xml_route import xml_api

__all__ = [
"basic_api",
"encoding_api",
"errors_api",
"multipart_api",
"streams_api",
"urlencoded_api",
"xml_api",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# coding: utf-8
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------

from flask import (
Response,
Blueprint,
request
)

basic_api = Blueprint('basic_api', __name__)

@basic_api.route('/string', methods=['GET'])
def string():
return Response(
"Hello, world!", status=200, mimetype="text/plain"
)

@basic_api.route('/lines', methods=['GET'])
def lines():
return Response(
"Hello,\nworld!", status=200, mimetype="text/plain"
)

@basic_api.route("/bytes", methods=['GET'])
def bytes():
return Response(
"Hello, world!".encode(), status=200, mimetype="text/plain"
)

@basic_api.route("/html", methods=['GET'])
def html():
return Response(
"<html><body>Hello, world!</html></body>", status=200, mimetype="text/html"
)

@basic_api.route("/json", methods=['GET'])
def json():
return Response(
'{"greeting": "hello", "recipient": "world"}', status=200, mimetype="application/json"
)

@basic_api.route("/complicated-json", methods=['POST'])
def complicated_json():
# thanks to Sean Kane for this test!
assert request.json['EmptyByte'] == ''
assert request.json['EmptyUnicode'] == ''
assert request.json['SpacesOnlyByte'] == ' '
assert request.json['SpacesOnlyUnicode'] == ' '
assert request.json['SpacesBeforeByte'] == ' Text'
assert request.json['SpacesBeforeUnicode'] == ' Text'
assert request.json['SpacesAfterByte'] == 'Text '
assert request.json['SpacesAfterUnicode'] == 'Text '
assert request.json['SpacesBeforeAndAfterByte'] == ' Text '
assert request.json['SpacesBeforeAndAfterUnicode'] == ' Text '
assert request.json['啊齄丂狛'] == 'ꀕ'
assert request.json['RowKey'] == 'test2'
assert request.json['啊齄丂狛狜'] == 'hello'
assert request.json["singlequote"] == "a''''b"
assert request.json["doublequote"] == 'a""""b'
assert request.json["None"] == None

return Response(status=200)
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# coding: utf-8
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------
from json import dumps
from flask import (
Response,
Blueprint,
)

encoding_api = Blueprint('encoding_api', __name__)

@encoding_api.route('/latin-1', methods=['GET'])
def latin_1():
r = Response(
"Latin 1: ÿ".encode("latin-1"), status=200
)
r.headers["Content-Type"] = "text/plain; charset=latin-1"
return r

@encoding_api.route('/latin-1-with-utf-8', methods=['GET'])
def latin_1_charset_utf8():
r = Response(
"Latin 1: ÿ".encode("latin-1"), status=200
)
r.headers["Content-Type"] = "text/plain; charset=utf-8"
return r

@encoding_api.route('/no-charset', methods=['GET'])
def latin_1_no_charset():
r = Response(
"Hello, world!", status=200
)
r.headers["Content-Type"] = "text/plain"
return r

@encoding_api.route('/iso-8859-1', methods=['GET'])
def iso_8859_1():
r = Response(
"Accented: Österreich".encode("iso-8859-1"), status=200
)
r.headers["Content-Type"] = "text/plain"
return r

@encoding_api.route('/emoji', methods=['GET'])
def emoji():
r = Response(
"👩", status=200
)
return r

@encoding_api.route('/emoji-family-skin-tone-modifier', methods=['GET'])
def emoji_family_skin_tone_modifier():
r = Response(
"👩🏻‍👩🏽‍👧🏾‍👦🏿 SSN: 859-98-0987", status=200
)
return r

@encoding_api.route('/korean', methods=['GET'])
def korean():
r = Response(
"아가", status=200
)
return r

@encoding_api.route('/json', methods=['GET'])
def json():
data = {"greeting": "hello", "recipient": "world"}
content = dumps(data).encode("utf-16")
r = Response(
content, status=200
)
r.headers["Content-Type"] = "application/json; charset=utf-16"
return r

@encoding_api.route('/invalid-codec-name', methods=['GET'])
def invalid_codec_name():
r = Response(
"おはようございます。".encode("utf-8"), status=200
)
r.headers["Content-Type"] = "text/plain; charset=invalid-codec-name"
return r

@encoding_api.route('/no-charset', methods=['GET'])
def no_charset():
r = Response(
"Hello, world!", status=200
)
r.headers["Content-Type"] = "text/plain"
return r
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# coding: utf-8
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------
from flask import (
Response,
Blueprint,
)

errors_api = Blueprint('errors_api', __name__)

@errors_api.route('/403', methods=['GET'])
def get_403():
return Response(status=403)

@errors_api.route('/500', methods=['GET'])
def get_500():
return Response(status=500)

@errors_api.route('/stream', methods=['GET'])
def get_stream():
class StreamingBody:
def __iter__(self):
yield b"Hello, "
yield b"world!"
return Response(StreamingBody(), status=500)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------

def assert_with_message(param_name, expected_value, actual_value):
assert expected_value == actual_value, "Expected '{}' to be '{}', got '{}'".format(
param_name, expected_value, actual_value
)

__all__ = ["assert_with_message"]
Loading