From c1fee218f350038c8e09919c8c56c713cf4038d6 Mon Sep 17 00:00:00 2001 From: Torsten Kilias Date: Mon, 29 Jan 2024 10:01:54 +0100 Subject: [PATCH] Add sanity check tests for the docker setup to the notebook tests --- .../test_notebooks_in_dss_docker_image.py | 5 +- test/notebooks/nbtest_environment_test.py | 9 + test/notebooks/nbtest_itde.py | 22 +++ test/notebooks/nbtest_links.py | 7 - test/notebooks/nbtest_sklearn.py | 18 -- test/notebooks/notebook_test_utils.py | 186 ------------------ 6 files changed, 35 insertions(+), 212 deletions(-) create mode 100644 test/notebooks/nbtest_environment_test.py create mode 100644 test/notebooks/nbtest_itde.py delete mode 100644 test/notebooks/nbtest_links.py delete mode 100644 test/notebooks/nbtest_sklearn.py delete mode 100644 test/notebooks/notebook_test_utils.py diff --git a/test/integration/test_notebooks_in_dss_docker_image.py b/test/integration/test_notebooks_in_dss_docker_image.py index 68cbaa71..7838fc10 100644 --- a/test/integration/test_notebooks_in_dss_docker_image.py +++ b/test/integration/test_notebooks_in_dss_docker_image.py @@ -67,4 +67,7 @@ def test_notebook(notebook_test_container, notebook_test_file): command_echo_virtual_env = 'bash -c "echo $VIRTUAL_ENV"' virtual_env = exec_command(command_echo_virtual_env, container) command_run_test = f'{virtual_env}/bin/python -m pytest --setup-show -s {notebook_test_file}' - exec_command(command_run_test, container, print_output=True, environment=os.environ) + environ = os.environ.copy() + environ["NBTEST_ACTIVE"] = "TRUE" + nbtest_environ = {key: value for key, value in environ if key.startswith("NBTEST_")} + exec_command(command_run_test, container, print_output=True, environment=nbtest_environ) diff --git a/test/notebooks/nbtest_environment_test.py b/test/notebooks/nbtest_environment_test.py new file mode 100644 index 00000000..32b28331 --- /dev/null +++ b/test/notebooks/nbtest_environment_test.py @@ -0,0 +1,9 @@ +import os + + +def test_assert_working_directory(): + assert os.getcwd() == os.environ["NOTEBOOK_FOLDER_INITIAL"] + + +def test_assert_environ_nbtest_active(): + assert os.environ["NBTEST_ACTIVE"] == "TRUE" diff --git a/test/notebooks/nbtest_itde.py b/test/notebooks/nbtest_itde.py new file mode 100644 index 00000000..6a9b8b02 --- /dev/null +++ b/test/notebooks/nbtest_itde.py @@ -0,0 +1,22 @@ +from exasol.secret_store import Secrets +from exasol.itde_manager import ( + bring_itde_up, + take_itde_down +) +from exasol.connections import open_pyexasol_connection + +def test_itde(tmp_path): + store_path = tmp_path / 'tmp_config.sqlite' + store_password = "password" + secrets = Secrets(store_path, master_password=store_password) + bring_itde_up(secrets) + try: + con = open_pyexasol_connection(secrets) + try: + result = con.execute("select 1").fetchmany() + assert result[0][0] == 1 + finally: + con.close() + finally: + take_itde_down(secrets) + diff --git a/test/notebooks/nbtest_links.py b/test/notebooks/nbtest_links.py deleted file mode 100644 index fa975d3c..00000000 --- a/test/notebooks/nbtest_links.py +++ /dev/null @@ -1,7 +0,0 @@ -from notebook_test_utils import verify_links_for_directory - - -def test_all_notebooks() -> None: - - missing_links = verify_links_for_directory() - assert not missing_links diff --git a/test/notebooks/nbtest_sklearn.py b/test/notebooks/nbtest_sklearn.py deleted file mode 100644 index f3322d73..00000000 --- a/test/notebooks/nbtest_sklearn.py +++ /dev/null @@ -1,18 +0,0 @@ -import os - -from notebook_test_utils import (access_to_temp_secret_store, notebook_runner) - - -def test_regression(notebook_runner) -> None: - - current_dir = os.getcwd() - try: - notebook_runner('main_config.ipynb') - os.chdir('./data') - notebook_runner('data_abalone.ipynb') - os.chdir('../sklearn') - notebook_runner('sklearn_predict_udf.ipynb') - notebook_runner('sklearn_train_abalone.ipynb') - notebook_runner('sklearn_predict_abalone.ipynb') - finally: - os.chdir(current_dir) diff --git a/test/notebooks/notebook_test_utils.py b/test/notebooks/notebook_test_utils.py deleted file mode 100644 index 66c9666e..00000000 --- a/test/notebooks/notebook_test_utils.py +++ /dev/null @@ -1,186 +0,0 @@ -from typing import Optional, List, Set, Tuple, Callable -from pathlib import Path -from itertools import chain -from functools import partial -import re -import random -import string - -import pytest -import nbformat -from nbclient import NotebookClient -import requests - -from exasol.secret_store import Secrets -from exasol.ai_lab_config import AILabConfig as CKey -from exasol.itde_manager import ( - bring_itde_up, - take_itde_down -) - - -def generate_password(pwd_length): - pwd_characters = string.ascii_letters + string.digits - return ''.join(random.choice(pwd_characters) for _ in range(pwd_length)) - - -def url_exists(url): - try: - response = requests.head(url) - return response.status_code < 400 - except requests.ConnectionError: - return False - - -def _init_secret_store(secrets: Secrets) -> None: - secrets.save(CKey.use_itde, 'yes') - secrets.save(CKey.mem_size, '4') - secrets.save(CKey.disk_size, '4') - secrets.save(CKey.db_schema, 'NOTEBOOK_TESTS') - - -def run_notebook(notebook_file: str, store_file: str, store_password: str, timeout: int = -1) -> None: - """ - Executes notebook with added access to the configuration store. - - Parameters: - notebook_file: Notebook file. - store_file: Configuration store file. - store_password: Configuration store password. - timeout: Optional timeout in seconds. - """ - - print(f"Run notebook: {notebook_file}", flush=True) - - # This code will be inserted at the beginning of the notebook - init_code = f''' - def init_notebook_test(): - from pathlib import Path - from exasol.secret_store import Secrets - global sb_config - sb_config = Secrets(Path("{store_file}"), "{store_password}") - init_notebook_test() - ''' - - # Read the notebook and insert a new cell with the above code into it. - nb = nbformat.read(notebook_file, as_version=4) - nb["cells"] = [nbformat.v4.new_code_cell(init_code)] + nb["cells"] - - # Execute the notebook object, expecting to get no exceptions. - def print_start_cell(cell, cell_index): - print(f"Starting cell {cell_index} with id {cell.id}", flush=True) - - nb_client = NotebookClient(nb, timeout=timeout, kernel_name='python3', on_cell_start=print_start_cell) - nb_client.execute() - - -@pytest.fixture -def access_to_temp_secret_store(tmp_path: Path) -> Tuple[Path, str]: - """ - Creates a temporary configuration store. - Brings up and subsequently destroys the Docker-DB. - Returns the temporary configuration store path and password. - """ - - # Create temporary secret store - store_path = tmp_path / 'tmp_config.sqlite' - # Should the password be constant or randomly generated is debatable. - # Strictly speaking, random password makes a test non-deterministic. - # On the other hand, it improves the security. If for some reason the - # temporary configuration store is not deleted, there will be no access - # to it since the password will be forgotten once the session is over. - store_password = generate_password(12) - secrets = Secrets(store_path, master_password=store_password) - - # Set the configuration required by the ITDE manager and those the - # manager will not set after starting the Docker-DB. - _init_secret_store(secrets) - - # Start the Docker-DB and then destroy it after the test finishes. - print("bring_itde_up") - bring_itde_up(secrets) - try: - yield store_path, store_password - finally: - take_itde_down(secrets) - - -@pytest.fixture -def notebook_runner(access_to_temp_secret_store) -> Callable: - """ - A fixture for running a notebook. - """ - - store_path, store_password = access_to_temp_secret_store - return partial(run_notebook, store_file=str(store_path), store_password=store_password) - - -def verify_links_for_notebook(nb_file: Path, - checked_urls: Optional[Set[str]] = None) -> List[Tuple[str, str]]: - """ - Verifies all links in the specified notebook. - Returns a list of invalid links[(nodebook name, link), (...)] - - Parameters: - nb_file: Notebook path - checked_urls: A set of already checked URLs. - """ - - nb = nbformat.read(str(nb_file), as_version=4) - - link_pattern = re.compile(r'\[.+?\]\((.+?)\)') - hyperlink_pattern = re.compile(r'.*?') - invalid_links: List[Tuple[str, str]] = [] - if checked_urls is None: - checked_urls = set() - - # Loop over all markdown cells in the notebook - for cell in nb.cells: - if cell.cell_type == 'markdown': - # Find all links or hyperlinks - for match in chain(link_pattern.finditer(cell.source), - hyperlink_pattern.finditer(cell.source)): - address = match.group(1) - address_lo = address.lower() - link_is_valid = True - if address_lo.startswith('http://') or address_lo.startswith('https://'): - # Check that the URL is accessible. - # Skip those that have already been checked. - if address not in checked_urls: - link_is_valid = url_exists(address) - checked_urls.add(address) - else: - # Check that the referenced file exists. - address_path = nb_file.parent / address - link_is_valid = address_path.exists() - if not link_is_valid: - invalid_links.append((nb_file.name, match.group(0))) - - return invalid_links - - -def verify_links_for_directory(nb_dir: Path = Path(''), include_subdir: bool = True, - checked_urls: Optional[Set[str]] = None) -> List[Tuple[str, str]]: - """ - Verifies links in all notebooks in the specified directory. - Returns a list of invalid links[(nodebook name, link), (...)] - - Parameters: - nb_dir: Directory path. - include_subdir: Include notebooks in all subdirectories at any depth. - checked_urls: A set of already checked URLs. - """ - - invalid_links: List[Tuple[str, str]] = [] - if checked_urls is None: - checked_urls = set() - - for child_obj in nb_dir.iterdir(): - if child_obj.is_dir(): - if include_subdir: - invalid_links.extend(verify_links_for_directory(child_obj, include_subdir, - checked_urls)) - elif child_obj.match('*.ipynb') and not child_obj.match('*-checkpoint.ipynb'): - invalid_links.extend(verify_links_for_notebook(child_obj, checked_urls)) - - return invalid_links