Skip to content

Commit 44e152a

Browse files
authored
adding mysql-connector driver contract test for some basic operations (#221)
*Issue #, if available:* N/A *Description of changes:* Adding contract tests for mysql-connector driver for MySQL. Includes `SELECT`, `CREATE DATABASE`, `DROP TABLE` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. ``` ./scripts/build_and_install_distro.sh && \ ./scripts/set-up-contract-tests.sh && \ python3 -m pytest contract-tests/tests/test/amazon/mysql-connector/mysql_connector_test.py ... ... .. .. ======================================================================= warnings summary ======================================================================== <frozen importlib._bootstrap>:488 <frozen importlib._bootstrap>:488: DeprecationWarning: Type google._upb._message.MessageMapContainer uses PyType_Spec with a metaclass that has custom tp_new. This is deprecated and will no longer be allowed in Python 3.14. <frozen importlib._bootstrap>:488 <frozen importlib._bootstrap>:488: DeprecationWarning: Type google._upb._message.ScalarMapContainer uses PyType_Spec with a metaclass that has custom tp_new. This is deprecated and will no longer be allowed in Python 3.14. -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html ================================================================ 4 passed, 2 warnings in 50.84s ================================================================= ``` ```
1 parent 1753bbf commit 44e152a

File tree

14 files changed

+269
-13
lines changed

14 files changed

+269
-13
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Meant to be run from aws-otel-python-instrumentation/contract-tests.
2+
# Assumes existence of dist/aws_opentelemetry_distro-<pkg_version>-py3-none-any.whl.
3+
# Assumes filename of aws_opentelemetry_distro-<pkg_version>-py3-none-any.whl is passed in as "DISTRO" arg.
4+
FROM python:3.10
5+
WORKDIR /mysql-connector
6+
COPY ./dist/$DISTRO /mysql-connector
7+
COPY ./contract-tests/images/applications/mysql-connector /mysql-connector
8+
9+
ENV PIP_ROOT_USER_ACTION=ignore
10+
ARG DISTRO
11+
RUN pip install --upgrade pip && pip install -r requirements.txt && pip install ${DISTRO} --force-reinstall
12+
RUN opentelemetry-bootstrap -a install
13+
14+
# Without `-u`, logs will be buffered and `wait_for_logs` will never return.
15+
CMD ["opentelemetry-instrument", "python", "-u", "./mysql_connector_server.py"]
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
import atexit
4+
import os
5+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
6+
from threading import Thread
7+
from typing import Tuple
8+
9+
import mysql.connector as mysql
10+
from typing_extensions import override
11+
12+
_SELECT: str = "select"
13+
_CREATE_DATABASE: str = "create_database"
14+
_DROP_TABLE: str = "drop_table"
15+
_FAULT: str = "fault"
16+
_PORT: int = 8080
17+
18+
_DB_HOST = os.getenv("DB_HOST")
19+
_DB_USER = os.getenv("DB_USER")
20+
_DB_PASS = os.getenv("DB_PASS")
21+
_DB_NAME = os.getenv("DB_NAME")
22+
23+
24+
class RequestHandler(BaseHTTPRequestHandler):
25+
@override
26+
# pylint: disable=invalid-name
27+
def do_GET(self):
28+
status_code: int = 200
29+
conn = mysql.connect(host=_DB_HOST, user=_DB_USER, password=_DB_PASS, database=_DB_NAME)
30+
conn.autocommit = True # CREATE DATABASE cannot run in a transaction block
31+
if self.in_path(_SELECT):
32+
cur = conn.cursor()
33+
cur.execute("SELECT count(*) FROM employee")
34+
result = cur.fetchall()
35+
cur.close()
36+
status_code = 200 if len(result) == 1 else 500
37+
elif self.in_path(_DROP_TABLE):
38+
cur = conn.cursor()
39+
cur.execute("DROP TABLE IF EXISTS test_table")
40+
cur.close()
41+
status_code = 200
42+
elif self.in_path(_CREATE_DATABASE):
43+
cur = conn.cursor()
44+
cur.execute("CREATE DATABASE test_database")
45+
cur.close()
46+
status_code = 200
47+
elif self.in_path(_FAULT):
48+
cur = conn.cursor()
49+
try:
50+
cur.execute("SELECT DISTINCT id, name FROM invalid_table")
51+
except mysql.ProgrammingError as exception:
52+
print("Expected Exception with Invalid SQL occurred:", exception)
53+
status_code = 500
54+
except Exception as exception: # pylint: disable=broad-except
55+
print("Exception Occurred:", exception)
56+
else:
57+
status_code = 200
58+
finally:
59+
cur.close()
60+
else:
61+
status_code = 404
62+
conn.close()
63+
self.send_response_only(status_code)
64+
self.end_headers()
65+
66+
def in_path(self, sub_path: str):
67+
return sub_path in self.path
68+
69+
70+
def prepare_db_server() -> None:
71+
conn = mysql.connect(host=_DB_HOST, user=_DB_USER, password=_DB_PASS, database=_DB_NAME)
72+
cur = conn.cursor()
73+
cur.execute("SHOW TABLES LIKE 'employee'")
74+
result = cur.fetchone()
75+
if not result:
76+
cur.execute("CREATE TABLE employee (id int, name varchar(255))")
77+
cur.execute("INSERT INTO employee (id, name) values (1, 'A')")
78+
cur.close()
79+
conn.close()
80+
81+
82+
def main() -> None:
83+
prepare_db_server()
84+
server_address: Tuple[str, int] = ("0.0.0.0", _PORT)
85+
request_handler_class: type = RequestHandler
86+
requests_server: ThreadingHTTPServer = ThreadingHTTPServer(server_address, request_handler_class)
87+
atexit.register(requests_server.shutdown)
88+
server_thread: Thread = Thread(target=requests_server.serve_forever)
89+
server_thread.start()
90+
print("Ready")
91+
server_thread.join()
92+
93+
94+
if __name__ == "__main__":
95+
main()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[project]
2+
name = "mysql-connector-server"
3+
description = "Simple server that relies on mysql-connector library"
4+
version = "1.0.0"
5+
license = "Apache-2.0"
6+
requires-python = ">=3.8"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
opentelemetry-distro==0.46b0
2+
opentelemetry-exporter-otlp-proto-grpc==1.25.0
3+
typing-extensions==4.9.0
4+
mysql-connector-python~=8.0

contract-tests/images/applications/mysqlclient/mysqlclient_server.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing_extensions import override
1212

1313
_PORT: int = 8080
14+
_SELECT: str = "select"
1415
_DROP_TABLE: str = "drop_table"
1516
_ERROR: str = "error"
1617
_FAULT: str = "fault"
@@ -29,7 +30,13 @@ def do_GET(self):
2930
status_code: int = 200
3031
conn = MySQLdb.connect(database=_DB_NAME, user=_DB_USER, password=_DB_PASS, host=_DB_HOST)
3132
conn.autocommit = True # CREATE DATABASE cannot run in a transaction block
32-
if self.in_path(_DROP_TABLE):
33+
if self.in_path(_SELECT):
34+
cur = conn.cursor()
35+
cur.execute("SELECT count(*) FROM employee")
36+
result = cur.fetchall()
37+
cur.close()
38+
status_code = 200 if len(result) == 1 else 500
39+
elif self.in_path(_DROP_TABLE):
3340
cur = conn.cursor()
3441
cur.execute("DROP TABLE IF EXISTS test_table")
3542
cur.close()
@@ -62,7 +69,20 @@ def in_path(self, sub_path: str):
6269
return sub_path in self.path
6370

6471

72+
def prepare_db_server() -> None:
73+
conn = MySQLdb.connect(host=_DB_HOST, user=_DB_USER, password=_DB_PASS, database=_DB_NAME)
74+
cur = conn.cursor()
75+
cur.execute("SHOW TABLES LIKE 'employee'")
76+
result = cur.fetchone()
77+
if not result:
78+
cur.execute("CREATE TABLE employee (id int, name varchar(255))")
79+
cur.execute("INSERT INTO employee (id, name) values (1, 'A')")
80+
cur.close()
81+
conn.close()
82+
83+
6584
def main() -> None:
85+
prepare_db_server()
6686
server_address: Tuple[str, int] = ("0.0.0.0", _PORT)
6787
request_handler_class: type = RequestHandler
6888
requests_server: ThreadingHTTPServer = ThreadingHTTPServer(server_address, request_handler_class)

contract-tests/images/applications/psycopg2/psycopg2_server.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing_extensions import override
1111

1212
_PORT: int = 8080
13+
_SELECT: str = "select"
1314
_DROP_TABLE: str = "drop_table"
1415
_ERROR: str = "error"
1516
_FAULT: str = "fault"
@@ -28,7 +29,13 @@ def do_GET(self):
2829
status_code: int = 200
2930
conn = psycopg2.connect(dbname=_DB_NAME, user=_DB_USER, password=_DB_PASS, host=_DB_HOST)
3031
conn.autocommit = True # CREATE DATABASE cannot run in a transaction block
31-
if self.in_path(_DROP_TABLE):
32+
if self.in_path(_SELECT):
33+
cur = conn.cursor()
34+
cur.execute("SELECT count(*) FROM employee")
35+
result = cur.fetchall()
36+
cur.close()
37+
status_code = 200 if len(result) == 1 else 500
38+
elif self.in_path(_DROP_TABLE):
3239
cur = conn.cursor()
3340
cur.execute("DROP TABLE IF EXISTS test_table")
3441
cur.close()
@@ -61,7 +68,21 @@ def in_path(self, sub_path: str):
6168
return sub_path in self.path
6269

6370

71+
def prepare_db_server() -> None:
72+
conn = psycopg2.connect(host=_DB_HOST, user=_DB_USER, password=_DB_PASS, database=_DB_NAME)
73+
cur = conn.cursor()
74+
cur.execute("SELECT to_regclass('public.employee')")
75+
result = cur.fetchone()[0]
76+
if not result:
77+
cur.execute("CREATE TABLE employee (id int, name varchar(255))")
78+
cur.execute("INSERT INTO employee (id, name) values (1, 'A')")
79+
conn.commit()
80+
cur.close()
81+
conn.close()
82+
83+
6484
def main() -> None:
85+
prepare_db_server()
6586
server_address: Tuple[str, int] = ("0.0.0.0", _PORT)
6687
request_handler_class: type = RequestHandler
6788
requests_server: ThreadingHTTPServer = ThreadingHTTPServer(server_address, request_handler_class)

contract-tests/images/applications/pymysql/pymysql_server.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pymysql
1010
from typing_extensions import override
1111

12+
_SELECT: str = "select"
1213
_CREATE_DATABASE: str = "create_database"
1314
_DROP_TABLE: str = "drop_table"
1415
_ERROR: str = "error"
@@ -28,7 +29,13 @@ def do_GET(self):
2829
status_code: int = 200
2930
conn = pymysql.connect(host=_DB_HOST, user=_DB_USER, password=_DB_PASS, database=_DB_NAME)
3031
conn.autocommit = True # CREATE DATABASE cannot run in a transaction block
31-
if self.in_path(_DROP_TABLE):
32+
if self.in_path(_SELECT):
33+
cur = conn.cursor()
34+
cur.execute("SELECT count(*) FROM employee")
35+
result = cur.fetchall()
36+
cur.close()
37+
status_code = 200 if len(result) == 1 else 500
38+
elif self.in_path(_DROP_TABLE):
3239
cur = conn.cursor()
3340
cur.execute("DROP TABLE IF EXISTS test_table")
3441
cur.close()
@@ -61,7 +68,20 @@ def in_path(self, sub_path: str):
6168
return sub_path in self.path
6269

6370

71+
def prepare_db_server() -> None:
72+
conn = pymysql.connect(host=_DB_HOST, user=_DB_USER, password=_DB_PASS, database=_DB_NAME)
73+
cur = conn.cursor()
74+
cur.execute("SHOW TABLES LIKE 'employee'")
75+
result = cur.fetchone()
76+
if not result:
77+
cur.execute("CREATE TABLE employee (id int, name varchar(255))")
78+
cur.execute("INSERT INTO employee (id, name) values (1, 'A')")
79+
cur.close()
80+
conn.close()
81+
82+
6483
def main() -> None:
84+
prepare_db_server()
6585
server_address: Tuple[str, int] = ("0.0.0.0", _PORT)
6686
request_handler_class: type = RequestHandler
6787
requests_server: ThreadingHTTPServer = ThreadingHTTPServer(server_address, request_handler_class)

contract-tests/tests/test/amazon/base/contract_test_base.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ def tear_down(self) -> None:
128128
def do_test_requests(
129129
self, path: str, method: str, status_code: int, expected_error: int, expected_fault: int, **kwargs
130130
) -> None:
131-
address: str = self.application.get_container_host_ip()
132-
port: str = self.application.get_exposed_port(self.get_application_port())
133-
url: str = f"http://{address}:{port}/{path}"
134-
response: Response = request(method, url, timeout=20)
131+
response: Response = self.send_request(method, path)
135132
self.assertEqual(status_code, response.status_code)
136133

137134
resource_scope_spans: List[ResourceScopeSpan] = self.mock_collector_client.get_traces()
@@ -145,6 +142,12 @@ def do_test_requests(
145142
self._assert_metric_attributes(metrics, ERROR_METRIC, expected_error, **kwargs)
146143
self._assert_metric_attributes(metrics, FAULT_METRIC, expected_fault, **kwargs)
147144

145+
def send_request(self, method, path) -> Response:
146+
address: str = self.application.get_container_host_ip()
147+
port: str = self.application.get_exposed_port(self.get_application_port())
148+
url: str = f"http://{address}:{port}/{path}"
149+
return request(method, url, timeout=20)
150+
148151
def _get_attributes_dict(self, attributes_list: List[KeyValue]) -> Dict[str, AnyValue]:
149152
attributes_dict: Dict[str, AnyValue] = {}
150153
for attribute in attributes_list:

contract-tests/tests/test/amazon/base/database_contract_test_base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ def assert_create_database_succeeds(self) -> None:
5858
self.mock_collector_client.clear_signals()
5959
self.do_test_requests("create_database", "GET", 200, 0, 0, sql_command="CREATE DATABASE")
6060

61+
def assert_select_succeeds(self) -> None:
62+
self.mock_collector_client.clear_signals()
63+
self.do_test_requests("select", "GET", 200, 0, 0, sql_command="SELECT")
64+
6165
def assert_fault(self) -> None:
6266
self.mock_collector_client.clear_signals()
6367
self.do_test_requests("fault", "GET", 500, 0, 1, sql_command="SELECT DISTINCT")
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from testcontainers.mysql import MySqlContainer
4+
from typing_extensions import override
5+
6+
from amazon.base.contract_test_base import NETWORK_NAME
7+
from amazon.base.database_contract_test_base import (
8+
DATABASE_HOST,
9+
DATABASE_NAME,
10+
DATABASE_PASSWORD,
11+
DATABASE_USER,
12+
DatabaseContractTestBase,
13+
)
14+
15+
16+
class MysqlConnectorTest(DatabaseContractTestBase):
17+
@override
18+
@classmethod
19+
def set_up_dependency_container(cls) -> None:
20+
cls.container = (
21+
MySqlContainer(MYSQL_USER=DATABASE_USER, MYSQL_PASSWORD=DATABASE_PASSWORD, MYSQL_DATABASE=DATABASE_NAME)
22+
.with_kwargs(network=NETWORK_NAME)
23+
.with_name(DATABASE_HOST)
24+
)
25+
cls.container.start()
26+
27+
@override
28+
@classmethod
29+
def tear_down_dependency_container(cls) -> None:
30+
cls.container.stop()
31+
32+
@override
33+
@staticmethod
34+
def get_remote_service() -> str:
35+
return "mysql"
36+
37+
@override
38+
@staticmethod
39+
def get_database_port() -> int:
40+
return 3306
41+
42+
@override
43+
@staticmethod
44+
def get_application_image_name() -> str:
45+
return "aws-application-signals-tests-mysql-connector-app"
46+
47+
def test_select_succeeds(self) -> None:
48+
self.assert_select_succeeds()
49+
50+
def test_create_database_succeeds(self) -> None:
51+
self.assert_create_database_succeeds()
52+
53+
def test_drop_table_succeeds(self) -> None:
54+
self.assert_drop_table_succeeds()
55+
56+
def test_fault(self) -> None:
57+
self.assert_fault()

0 commit comments

Comments
 (0)