blob: 4869899aeff76495532452657f494385e71c31aa [file] [log] [blame]
# Copyright 2015 gRPC authors.
#
# 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.
"""Provides setuptools command classes for the gRPC Python setup process."""
import glob
import os
import os.path
import platform
import re
import shutil
import sys
import setuptools
from setuptools import errors as _errors
from setuptools.command import build_ext
from setuptools.command import build_py
from setuptools.command import easy_install
from setuptools.command import install
from setuptools.command import test
PYTHON_STEM = os.path.dirname(os.path.abspath(__file__))
GRPC_STEM = os.path.abspath(PYTHON_STEM + "../../../../")
GRPC_PROTO_STEM = os.path.join(GRPC_STEM, "src", "proto")
PROTO_STEM = os.path.join(PYTHON_STEM, "src", "proto")
PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_STEM, "src")
class CommandError(object):
pass
class GatherProto(setuptools.Command):
description = "gather proto dependencies"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
# TODO(atash) ensure that we're running from the repository directory when
# this command is used
try:
shutil.rmtree(PROTO_STEM)
except Exception as error:
# We don't care if this command fails
pass
shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM)
for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL):
path = os.path.join(root, "__init__.py")
open(path, "a").close()
class BuildPy(build_py.build_py):
"""Custom project build command."""
def run(self):
try:
self.run_command("build_package_protos")
except CommandError as error:
sys.stderr.write("warning: %s\n" % error.message)
build_py.build_py.run(self)
class TestLite(setuptools.Command):
"""Command to run tests without fetching or building anything."""
description = "run tests without fetching or building anything."
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
# distutils requires this override.
pass
def run(self):
import tests
loader = tests.Loader()
loader.loadTestsFromNames(["tests"])
runner = tests.Runner(dedicated_threads=True)
result = runner.run(loader.suite)
if not result.wasSuccessful():
sys.exit("Test failure")
class TestPy3Only(setuptools.Command):
"""Command to run tests for Python 3+ features.
This does not include asyncio tests, which are housed in a separate
directory.
"""
description = "run tests for py3+ features"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
import tests
loader = tests.Loader()
loader.loadTestsFromNames(["tests_py3_only"])
runner = tests.Runner()
result = runner.run(loader.suite)
if not result.wasSuccessful():
sys.exit("Test failure")
class TestAio(setuptools.Command):
"""Command to run aio tests without fetching or building anything."""
description = "run aio tests without fetching or building anything."
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
import tests
loader = tests.Loader()
loader.loadTestsFromNames(["tests_aio"])
# Even without dedicated threads, the framework will somehow spawn a
# new thread for tests to run upon. New thread doesn't have event loop
# attached by default, so initialization is needed.
runner = tests.Runner(dedicated_threads=False)
result = runner.run(loader.suite)
if not result.wasSuccessful():
sys.exit("Test failure")
class RunInterop(test.test):
description = "run interop test client/server"
user_options = [
("args=", None, "pass-thru arguments for the client/server"),
("client", None, "flag indicating to run the client"),
("server", None, "flag indicating to run the server"),
("use-asyncio", None, "flag indicating to run the asyncio stack"),
]
def initialize_options(self):
self.args = ""
self.client = False
self.server = False
self.use_asyncio = False
def finalize_options(self):
if self.client and self.server:
raise _errors.OptionError(
"you may only specify one of client or server"
)
def run(self):
if self.client:
self.run_client()
elif self.server:
self.run_server()
def run_server(self):
# We import here to ensure that our setuptools parent has had a chance to
# edit the Python system path.
if self.use_asyncio:
import asyncio
from tests_aio.interop import server
sys.argv[1:] = self.args.split()
args = server.parse_interop_server_arguments(sys.argv)
asyncio.get_event_loop().run_until_complete(server.serve(args))
else:
from tests.interop import server
sys.argv[1:] = self.args.split()
server.serve(server.parse_interop_server_arguments(sys.argv))
def run_client(self):
# We import here to ensure that our setuptools parent has had a chance to
# edit the Python system path.
from tests.interop import client
sys.argv[1:] = self.args.split()
client.test_interoperability(client.parse_interop_client_args(sys.argv))
class RunFork(test.test):
description = "run fork test client"
user_options = [("args=", "a", "pass-thru arguments for the client")]
def initialize_options(self):
self.args = ""
def finalize_options(self):
# distutils requires this override.
pass
def run(self):
# We import here to ensure that our setuptools parent has had a chance to
# edit the Python system path.
from tests.fork import client
sys.argv[1:] = self.args.split()
client.test_fork()