You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/06/05 12:57:58 UTC

[skywalking-python] 01/01: Test: set up plugin tests and add http plugin test

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 <ke...@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="dev@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="dev@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="dev@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="dev@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="dev@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="dev@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="dev@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()