From 3b05fb9389a5a2a2b51b39b01524dac2d0cd1fdd Mon Sep 17 00:00:00 2001 From: Torsten Kilias Date: Tue, 31 Oct 2023 15:16:35 +0100 Subject: [PATCH] Add tests for pyexasol with fingerprints and add a test which imports all python modules. --- .../standard-flavor/all/import_modules.py | 203 ++++++++++++++++++ .../test/standard-flavor/all/pyexasol.py | 66 ++++++ 2 files changed, 269 insertions(+) create mode 100644 test_container/tests/test/standard-flavor/all/import_modules.py create mode 100644 test_container/tests/test/standard-flavor/all/pyexasol.py diff --git a/test_container/tests/test/standard-flavor/all/import_modules.py b/test_container/tests/test/standard-flavor/all/import_modules.py new file mode 100644 index 000000000..e637da1e0 --- /dev/null +++ b/test_container/tests/test/standard-flavor/all/import_modules.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf.udf_debug import UdfDebugger + +class ScikitLearnTest(udf.TestCase): + + def setUp(self): + self.query('create schema scikit_learn', ignore_errors=True) + + def test_import_scikit_learn_bug_836(self): + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT scikit_learn.import_scikit_learn() + EMITS (module_name VARCHAR(200000), exception_str VARCHAR(200000), status VARCHAR(10)) AS + + import sys + import os + import pkgutil + import importlib + from typing import List + + module_limit = 10000 + error_limit = 100 + excluded_modules = { + "turtle", + "py310compat", + "urllib3.contrib.socks", + "urllib3.contrib.securetransport", + "urllib3.contrib.ntlmpool", + "simplejson.ordered_dict", + "pymemcache.test", + "sagemaker.feature_store.feature_processor", + "sagemaker.remote_function.runtime_environment.spark_app", + "pyftpdlib.test", + "paramiko.win_pageant", + "pandas.core.arrays.arrow", + "msal_extensions.windows", + "msal_extensions.osx", + "msal_extensions.libsecret", + "lxml.usedoctest", + "lxml.html.usedoctest", + "lxml.html.soupparser", + "lxml.html.html5parser", + "lxml.html.ElementSoup", + "lxml.cssselect", + "jsonschema.benchmarks", + "numpy.f2py.setup", + "numpy.distutils.msvc9compiler", + "numpy.core.setup_common", + "numpy.core.setup", + "numpy.core.generate_numpy_api", + "numpy.core.cversions", + "multiprocess.popen_spawn_win32", + "msal.broker", + "joblib.externals.loky.backend.popen_loky_win32", + "ijson.backends.yajl2_cffi", + "ijson.backends.yajl2", + "ijson.backends.yajl", + "docutils.parsers", + "dateutil.tzwin", + "dateutil.tz.win", + "botocore.docs", + "boto.roboto.awsqueryrequest", + "boto.roboto.awsqueryservice", + "boto.s3.resumable_download_handler", + "boto.manage.test_manage", + "boto.gs.resumable_upload_handler", + "boto.mashups.order", + "boto.pyami.copybot", + "boto.requestlog", + "boto.gs.resumable_upload_handler", + "sagemaker.content_types", + "pyarrow.libarrow_python_flight", + "pyarrow.libarrow_python", + "pyarrow.cuda", + "numba.testing.notebook", + "numba.np.ufunc.tbbpool", + "numba.misc.gdb_print_extension", + "numba.misc.dump_style", + "martian.testing_compat3", + "llvmlite.binding.libllvmlite", + "docutils.writers.odf_odt.pygmentsformatter", + "debugpy.launcher.winapi", + "bitsets.visualize", + "aiohttp.worker", + "test.libregrtest.win_utils", + "multiprocessing.popen_spawn_win32", + "lib2to3.pgen2.conv", + "encodings.oem", + "encodings.mbcs", + "distutils.msvc9compiler", + "dbm.gnu", + "asyncio.windows_utils", + "asyncio.windows_events", + "Cython.Debugger", + "Cython.Build.Tests", + "Cython.Build.IpythonMagic", + "Cython.Coverage" + } + excluded_submodules = ( + "sphinxext", + "tests", + "conftest", + ) + + def get_module_names(path): + modules = list(module for _, module, _ in pkgutil.iter_modules(path)) + return modules + + class Importer: + + def __init__( + self, ctx, error_limit: int, module_limit: int, + excluded_modules: List[str], excluded_submodules: List[str]): + + self.ctx = ctx + self.error_count = 0 + self.module_count = 0 + self.error_limit = error_limit + self.module_limit = module_limit + self.excluded_modules = excluded_modules + self.excluded_submodules = excluded_submodules + self.modules = [] + + def module_limit_reached(self) -> bool: + self.module_count += 1 + return self.module_count > self.module_limit + + def error_limit_reached(self) -> bool: + self.error_count += 1 + return self.error_count > self.error_limit + + def import_modules(self): + self.modules = get_module_names(sys.path) + while len(self.modules) > 0: + module = self.modules.pop() + limit_reached = self.import_module(module) + if limit_reached: + break + + def add_submodules(self, module, module_import): + if hasattr(module_import,"__path__"): + submodule_names = get_module_names(module_import.__path__) + self.modules.extend([ + f"{module}.{submodule}" + for submodule in submodule_names + if not submodule.startswith("_") and submodule not in self.excluded_submodules + ]) + + def import_module(self, module: str) -> bool: + print("========================================================") + print("========================================================") + print("========================================================") + print("modules left: ", len(self.modules)) + print("current module: ", module) + print("========================================================") + print("========================================================") + print("========================================================", flush=True) + if module in self.excluded_modules or module.startswith("_"): + self.ctx.emit(module, None, "SKIPPED") + return self.module_limit_reached() + try: + module_import = importlib.import_module(module) + self.ctx.emit(module, None, "OK") + self.add_submodules(module=module, module_import=module_import) + return self.module_limit_reached() + except BaseException as e: + import traceback + if hasattr(e,"msg") and e.msg == "No module named 'pytest'": + self.ctx.emit(module, None, "IGNORED") + return self.module_limit_reached() + else: + self.ctx.emit(module, traceback.format_exc(), "ERROR") + return self.error_limit_reached() + + def run(ctx): + importer = Importer( + ctx=ctx, + error_limit = error_limit, + module_limit = module_limit, + excluded_modules = excluded_modules, + excluded_submodules = excluded_submodules) + importer.import_modules() + / + ''')) + with UdfDebugger(test_case=self): + rows = self.query('''SELECT scikit_learn.import_scikit_learn() FROM dual''') + print("Number of modules:",len(rows)) + failed_imports = [(row[0],row[1]) for row in rows if row[2] == "ERROR"] + for i in failed_imports: + print(i[0]) + for i in failed_imports: + print(i[0], i[1]) + self.assertEqual(failed_imports,[]) + + + def tearDown(self): + self.query("drop schema scikit_learn cascade") + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/standard-flavor/all/pyexasol.py b/test_container/tests/test/standard-flavor/all/pyexasol.py new file mode 100644 index 000000000..e53bb242e --- /dev/null +++ b/test_container/tests/test/standard-flavor/all/pyexasol.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + + +from exasol_python_test_framework import udf + + +class PyexsolConnectionTest(udf.TestCase): + # TODO use dsn and credentials injected into the testcase + host = "localhost" + port = "8888" + user = "sys" + pwd = "exasol" + + def setUp(self): + self.query('create schema pyexasol', ignore_errors=True) + + def run_secure_pyexasol_connection(self, python_version): + self.query(udf.fixindent(''' + CREATE OR REPLACE {python} SCALAR SCRIPT pyexasol.connect_secure() returns int AS + import pyexasol + import ssl + import os + def run(ctx): + os.environ["USER"]="exasolution" + with pyexasol.connect( + dsn='{host}:{port}', user='{user}', password='{pwd}', + websocket_sslopt={{"cert_reqs": ssl.CERT_NONE}}, encryption=True) as connection: + result_set = connection.execute('SELECT 1 FROM dual') + for row in result_set: + pass + / + '''.format(python=python_version, host=self.host, port=self.port, user=self.user, pwd=self.pwd))) + self.query('''SELECT pyexasol.connect_secure() FROM dual''') + + def run_fingerprint_pyexasol_connection(self, python_version): + self.query(udf.fixindent(''' + CREATE OR REPLACE {python} SCALAR SCRIPT pyexasol.connect_secure() returns VARCHAR(2000000) AS + import pyexasol + import ssl + import os + def run(ctx): + os.environ["USER"]="exasolution" + try: + with pyexasol.connect( + dsn='{host}/135a1d2dce102de866f58267521f4232153545a075dc85f8f7596f57e588a181:{port}', + user='{user}', password='{pwd}') as connection: + pass + except pyexasol.ExaConnectionFailedError as e: + return e.message + / + '''.format(python=python_version, host=self.host, port=self.port, user=self.user, pwd=self.pwd))) + rows=self.query('''SELECT pyexasol.connect_secure() FROM dual''') + self.assertRegex(rows[0][0], r"Provided fingerprint.*did not match server fingerprint") + + def test_secure_pyexasol_connection_python3(self): + self.run_secure_pyexasol_connection("PYTHON3") + + def test_fingerprint_pyexasol_connection_python3(self): + self.run_fingerprint_pyexasol_connection("PYTHON3") + + def tearDown(self): + self.query("drop schema pyexasol cascade") + + +if __name__ == '__main__': + udf.main()