This is an automated email from the ASF dual-hosted git repository. kezhenxu94 pushed a commit to branch plugin-test in repository https://gitbox.apache.org/repos/asf/skywalking-python.git
commit 5e02ed82205d41cdc45f942c633501db0f5a78bb Author: kezhenxu94 <kezhenx...@163.com> AuthorDate: Fri Jun 5 20:57:35 2020 +0800 Test: set up plugin tests and add http plugin test --- docs/FAQ.md | 2 +- docs/PluginTest.md | 32 +++++++++++ setup.py | 3 + tests/plugin/__init__.py | 71 ++++++++++++++++++++++++ setup.py => tests/plugin/docker/Dockerfile.agent | 28 ++-------- setup.py => tests/plugin/docker/Dockerfile.tool | 52 ++++++++--------- setup.py => tests/plugin/http/__init__.py | 25 --------- setup.py => tests/plugin/http/docker-compose.yml | 56 +++++++++++-------- setup.py => tests/plugin/http/expected.data.yml | 45 +++++++-------- setup.py => tests/plugin/http/provider.py | 51 ++++++++--------- setup.py => tests/plugin/http/test_request.py | 56 ++++++++++--------- 11 files changed, 250 insertions(+), 171 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 55e7b0c..eaefae7 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,4 +1,4 @@ -## FAQ +# FAQ Q: How to disable some plugins? A: You can find the plugin name in [the list](../README.md#supported-libraries) and disable one or more plugins by following methods. diff --git a/docs/PluginTest.md b/docs/PluginTest.md new file mode 100644 index 0000000..a10ea80 --- /dev/null +++ b/docs/PluginTest.md @@ -0,0 +1,32 @@ +# Plugin Test + +Plugin test is required and should pass before a new plugin is able to be merged into the master branch. + +## Mock Collector + +Mock Collector respects the same protocol as the SkyWalking backend, and thus receives the report data from the agent side, +besides, it also exposes some http endpoints for verification. + +## Tested Service + +A tested service is a service involving the plugin that is to be tested, and exposes some endpoints to trigger the instrumentation +and report data to the mock collector. + +## Docker Compose + +`docker-compose` is used to orchestrate the mock collector and the tested service(s), the `docker-compose.yml` should be +able to run with `docker-compose -f docker-compose.yml up` in standalone mode, which can be used in debugging too. + +## Expected Data + +The `expected.data.yml` file contains the expected segment data after we have triggered the instrumentation and report to mock collector, +since the mock collector received the segment data then, we can post the expected data to the mock collector and verify whether +they match. This can be done through the `/dataValidate` of the mock collector, say `http://collector:12800/dataValidate`, for example. + +## Example + +If we want to test the plugin for the built-in library `http`, we will: + +1. Build a tested service, which sets up an http server by `http` library, and exposes an http endpoint to be triggered in the test codes, say `/trigger`, take [this provider service](../tests/plugin/http/provider.py) as example. +1. Compose a `docker-compose.yml` file, orchestrating the service built in step 1 and the mock collector, take [this `docker-compose.yml`](../tests/plugin/http/docker-compose.yml) as example. +1. Write test codes to trigger the endpoint int step 1, and send the expected data file to the mock collector to verify, take [this test](../tests/plugin/http/test_request.py) as example. diff --git a/setup.py b/setup.py index b5102f1..987b792 100644 --- a/setup.py +++ b/setup.py @@ -39,4 +39,7 @@ setup( "grpcio", "requests", ], + tests_require=[ + "testcontainers" + ], ) diff --git a/tests/plugin/__init__.py b/tests/plugin/__init__.py new file mode 100644 index 0000000..5bb74c8 --- /dev/null +++ b/tests/plugin/__init__.py @@ -0,0 +1,71 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +import os +import unittest +from abc import ABC +from collections import namedtuple + +import requests +from requests import Response +from testcontainers.compose import DockerCompose + +HostPort = namedtuple('HostPort', 'host port') +ServicePort = namedtuple('ServicePort', 'service port') + + +class BasePluginTest(unittest.TestCase, ABC): + compose = None # type: DockerCompose + + @classmethod + def tearDownClass(cls): + cls.compose.stop() + + @classmethod + def host(cls, service_port): + # type: (ServicePort) -> str + service, port = service_port + return cls.compose.get_service_host(service_name=service, port=port) + + @classmethod + def port(cls, service_port): + # type: (ServicePort) -> str + service, port = service_port + return cls.compose.get_service_port(service_name=service, port=port) + + @classmethod + def url(cls, service_port, path=''): + # type: (ServicePort, str) -> str + return 'http://%s:%s/%s' % (cls.host(service_port), cls.port(service_port), path.lstrip('/')) + + @classmethod + def collector_address(cls): + # type: () -> ServicePort + return ServicePort(service='collector', port='12800') + + def validate(self, expected_file_name): + # type: (str) -> Response + with open(expected_file_name) as expected_data_file: + response = requests.post( + url=self.__class__.url(self.__class__.collector_address(), path='/dataValidate'), + data=os.linesep.join(expected_data_file.readlines()), + ) + print('validate: ', response) + + self.assertEqual(response.status_code, 200) + + return response diff --git a/setup.py b/tests/plugin/docker/Dockerfile.agent similarity index 55% copy from setup.py copy to tests/plugin/docker/Dockerfile.agent index b5102f1..7672618 100644 --- a/setup.py +++ b/tests/plugin/docker/Dockerfile.agent @@ -1,4 +1,3 @@ -# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -13,30 +12,13 @@ # 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. -# -import pathlib +FROM python:3.7 -from setuptools import setup, find_packages +ARG ROOT=. -HERE = pathlib.Path(__file__).parent +WORKDIR /agent -README = (HERE / "README.md").read_text() +ADD $ROOT /agent -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) +RUN make setup install diff --git a/setup.py b/tests/plugin/docker/Dockerfile.tool similarity index 55% copy from setup.py copy to tests/plugin/docker/Dockerfile.tool index b5102f1..f4283f4 100644 --- a/setup.py +++ b/tests/plugin/docker/Dockerfile.tool @@ -15,28 +15,30 @@ # limitations under the License. # -import pathlib - -from setuptools import setup, find_packages - -HERE = pathlib.Path(__file__).parent - -README = (HERE / "README.md").read_text() - -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) +FROM openjdk:8 + +WORKDIR /tests + +ARG COMMIT_HASH=3c9d7099f05dc4a4b937c8a47506e56c130b6221 + +ADD https://github.com/apache/skywalking-agent-test-tool/archive/${COMMIT_HASH}.tar.gz . + +RUN tar -xf ${COMMIT_HASH}.tar.gz --strip 1 + +RUN rm ${COMMIT_HASH}.tar.gz + +RUN ./mvnw -B -DskipTests package + +FROM openjdk:8 + +EXPOSE 19876 12800 + +WORKDIR /tests + +COPY --from=0 /tests/dist/skywalking-mock-collector.tar.gz /tests + +RUN tar -xf skywalking-mock-collector.tar.gz --strip 1 + +RUN chmod +x bin/collector-startup.sh + +ENTRYPOINT bin/collector-startup.sh diff --git a/setup.py b/tests/plugin/http/__init__.py similarity index 55% copy from setup.py copy to tests/plugin/http/__init__.py index b5102f1..6222972 100644 --- a/setup.py +++ b/tests/plugin/http/__init__.py @@ -15,28 +15,3 @@ # limitations under the License. # -import pathlib - -from setuptools import setup, find_packages - -HERE = pathlib.Path(__file__).parent - -README = (HERE / "README.md").read_text() - -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) diff --git a/setup.py b/tests/plugin/http/docker-compose.yml similarity index 50% copy from setup.py copy to tests/plugin/http/docker-compose.yml index b5102f1..c43fee3 100644 --- a/setup.py +++ b/tests/plugin/http/docker-compose.yml @@ -15,28 +15,40 @@ # limitations under the License. # -import pathlib +version: '2.1' -from setuptools import setup, find_packages +services: + collector: + build: + context: ../docker + dockerfile: Dockerfile.tool + ports: + - 19876:19876 + - 12800:12800 + networks: + - beyond + healthcheck: + test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/12800"] + interval: 5s + timeout: 60s + retries: 120 -HERE = pathlib.Path(__file__).parent + provider: + build: + context: ../../../ + dockerfile: tests/plugin/docker/Dockerfile.agent + networks: + - beyond + ports: + - 9091:9091 + volumes: + - ./provider.py:/app/provider.py + environment: + SW_AGENT_COLLECTOR_BACKEND_SERVICES: collector:19876 + command: ['python3', '/app/provider.py'] + depends_on: + collector: + condition: service_healthy -README = (HERE / "README.md").read_text() - -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) +networks: + beyond: diff --git a/setup.py b/tests/plugin/http/expected.data.yml similarity index 55% copy from setup.py copy to tests/plugin/http/expected.data.yml index b5102f1..644f42b 100644 --- a/setup.py +++ b/tests/plugin/http/expected.data.yml @@ -15,28 +15,23 @@ # limitations under the License. # -import pathlib - -from setuptools import setup, find_packages - -HERE = pathlib.Path(__file__).parent - -README = (HERE / "README.md").read_text() - -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) +segmentItems: + - serviceName: provider + segmentSize: 1 + segments: + - segmentId: not null + spans: + - operationName: / + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 7000 + isError: false + spanType: Entry + peer: not null + skipAnalysis: false + tags: + - {key: http.method, value: POST} diff --git a/setup.py b/tests/plugin/http/provider.py similarity index 51% copy from setup.py copy to tests/plugin/http/provider.py index b5102f1..7af884a 100644 --- a/setup.py +++ b/tests/plugin/http/provider.py @@ -15,28 +15,29 @@ # limitations under the License. # -import pathlib - -from setuptools import setup, find_packages - -HERE = pathlib.Path(__file__).parent - -README = (HERE / "README.md").read_text() - -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) +import time + +from skywalking import agent, config + +if __name__ == '__main__': + config.service_name = 'provider' + config.logging_level = 'DEBUG' + agent.start() + + import socketserver + from http.server import BaseHTTPRequestHandler + + class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): + + def do_POST(self): + time.sleep(0.5) + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write('{"song": "Despacito", "artist": "Luis Fonsi"}'.encode('ascii')) + + PORT = 9091 + Handler = SimpleHTTPRequestHandler + + with socketserver.TCPServer(("", PORT), Handler) as httpd: + httpd.serve_forever() diff --git a/setup.py b/tests/plugin/http/test_request.py similarity index 50% copy from setup.py copy to tests/plugin/http/test_request.py index b5102f1..1619dd8 100644 --- a/setup.py +++ b/tests/plugin/http/test_request.py @@ -15,28 +15,34 @@ # limitations under the License. # -import pathlib - -from setuptools import setup, find_packages - -HERE = pathlib.Path(__file__).parent - -README = (HERE / "README.md").read_text() - -setup( - name="skywalking-python", - version="0.1.1", - description="Python Agent for Apache SkyWalking", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/apache/skywalking-python/", - author="Apache", - author_email="d...@skywalking.apache.org", - license="Apache 2.0", - packages=find_packages(exclude=("tests",)), - include_package_data=True, - install_requires=[ - "grpcio", - "requests", - ], -) +import os +import time +import unittest +from os.path import abspath, dirname + +import requests +from testcontainers.compose import DockerCompose + +from tests.plugin import BasePluginTest + + +class TestRequestPlugin(BasePluginTest): + @classmethod + def setUpClass(cls): + docker_dir = dirname(dirname(abspath(__file__))) + + cls.compose = DockerCompose(filepath=os.path.join(docker_dir, 'http')) + cls.compose.start() + + cls.compose.wait_for(cls.url(cls.collector_address())) + + def test_request_plugin(self): + print('traffic: ', requests.post(url=self.url(('provider', '9091')))) + + time.sleep(3) + + self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 'expected.data.yml')) + + +if __name__ == '__main__': + unittest.main()