Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#10 add fixtures #13

Merged
merged 58 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cebd3a3
#10: Added pytest fixtures for usage in integration tests of externa…
ckunki May 17, 2024
ef423fe
Merge branch 'main' into feature/#10-add_fixtures
ckunki May 17, 2024
8121b53
Apply suggestions from code review
ckunki May 17, 2024
6190da3
Use pyyaml for reading project short tag from error_code_config.yml
ckunki May 22, 2024
6c76a2c
Use pyyaml for reading project short tag from error_code_config.yml
ckunki May 22, 2024
6f48ebd
Fixed review findings
ckunki May 22, 2024
bc72a63
Abort shellscript in just file on errors
ckunki May 22, 2024
37198fe
Added secrets for integration tests
ckunki May 22, 2024
1e76646
Replaced bash script in justfile by python
ckunki May 22, 2024
b68d9f2
Separated long running test cases
ckunki May 22, 2024
cc9ecbb
Separated long running test cases
ckunki May 22, 2024
549f201
Separated long running test cases
ckunki May 22, 2024
24a82d9
Fixed if statement in github worflow
ckunki May 22, 2024
e83d749
Fixed if statement in github worflow
ckunki May 22, 2024
5f89ffe
Experiment event
ckunki May 22, 2024
bdd3eb5
Update ci.yml
ckunki May 22, 2024
f20e225
Update ci.yml
ckunki May 22, 2024
70eb743
Update ci.yml
ckunki May 22, 2024
7d81626
move commit message to input
ckunki May 22, 2024
184ea40
move commit message to input
ckunki May 22, 2024
64db909
experiment 1
ckunki May 22, 2024
9c88ed6
experiment 2
ckunki May 22, 2024
ada2d32
experiment 3
ckunki May 22, 2024
5a847d5
experiment 4
ckunki May 22, 2024
e1bf00e
experiment 5
ckunki May 22, 2024
e3519aa
experiment 6
ckunki May 22, 2024
18cdc24
experiment 7
ckunki May 22, 2024
f38ee40
experiment 8
ckunki May 22, 2024
71e8b4c
experiment 9
ckunki May 22, 2024
615b4a4
experiment 10
ckunki May 22, 2024
08c0a65
experiment 11
ckunki May 22, 2024
e9d1e7d
experiment 12
ckunki May 22, 2024
68b4c98
experiment 13
ckunki May 22, 2024
7edd50e
experiment 14
ckunki May 22, 2024
9c5964d
experiment 15
ckunki May 22, 2024
292b7f6
experiment 16
ckunki May 22, 2024
4df961a
experiment 17
ckunki May 22, 2024
00dcb79
experiment 18
ckunki May 22, 2024
e1b57b4
experiment 19
ckunki May 22, 2024
ec5240f
experiment 20
ckunki May 22, 2024
e676eef
experiment 21
ckunki May 22, 2024
191d7ee
experiment 22
ckunki May 22, 2024
164ba41
experiment 23
ckunki May 22, 2024
997f4d5
Apply suggestions from code review
ckunki May 22, 2024
9df4d15
clean up
ckunki May 22, 2024
6125dcd
Added developer instructions
ckunki May 22, 2024
ff1d2b1
Added pytest marker "slow" to pytest-itde, too
ckunki May 22, 2024
58e8c6b
May 23rd workflow experiment 1
ckunki May 23, 2024
533fc15
May 23rd workflow experiment 2
ckunki May 23, 2024
4490332
May 23rd workflow experiment 3
ckunki May 23, 2024
013645b
May 23rd workflow experiment 4
ckunki May 23, 2024
f677941
May 23rd workflow experiment 5
ckunki May 23, 2024
a846e91
May 23rd workflow experiment 6
ckunki May 23, 2024
3a77020
May 23rd workflow experiment 7
ckunki May 23, 2024
28efea4
Changed workflows to use input instead of commit messages
ckunki May 23, 2024
b8d654b
Updated README file
ckunki May 23, 2024
6456608
Reverted separation of slow tests
ckunki May 27, 2024
99bca15
Updated README
ckunki May 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions pytest-saas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,43 @@ To install the pytest-exasol-saas plugin, you can use pip:
```shell
pip install pytest-exasol-saas
```

## Database Instances

### Using an Existing Database Instance

By default the fixtures in pytest-exasol-saas Plugin will create instances of Exasol SaaS database with scope `session`.

If you want to use an existing instance instead, then you can provide the instance's ID with command line option `--saas-database-id <ID>` to pytest.
ckunki marked this conversation as resolved.
Show resolved Hide resolved

### Keeping Database Instances After the Test Session

By default the fixtures in pytest-exasol-saas Plugin will remove the created database instances after the session or in case of errors.

However, if you provide command line option `--keep-saas-database` then pytest-exasol-saas Plugin will _keep_ these instances for subsequent inspection or reuse.
ckunki marked this conversation as resolved.
Show resolved Hide resolved

Please note hat long-running instances will cause significant costs.

### Naming Database Instances

pytest-exasol-saas Plugin will name the database instances using 3 components

* **Project Short Tag**: Abbreviation of the current project, see below for different [options for providing the project short tag](#options-for-providing-the-project-short-tag).
* **Timestamp**: Number of seconds since epoc, see [Unix Time](https://en.wikipedia.org/wiki/Unix_time).
* A dash character `-`
* **User Name**: Login name of the current user.

A database instances might for example have the name `1715155224SAPIPY-run` indicating it was
* created on Wednesday, May 8, 2024
* in the context of a project with short tag `SAPYPI`
ckunki marked this conversation as resolved.
Show resolved Hide resolved
* by a user with login name starting with `run`

Please note that Exasol SaaS limits the length of database names to 10 characters only. So pytest-exasol-saas plugin will shorten the constructed name to 10 characters max.

If running your tests on a server for Continuous Integration (CI) then the name of the user might be not very expressive.

### Options for providing the Project Short Tag

* In yaml file `error_code_config.yml` in the project's root directory
* CLI option `--project-short-tag <short tag>` to pytest
* Environment variable `PROJECT_SHORT_TAG`
1 change: 1 addition & 0 deletions pytest-saas/doc/changes/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ This version introduces the `pytest-exasol-saas` plugin providing pytest functio
## Features

* #9: Added sub-project for exasol-saas-api
* #10: Added pytest fixtures for usage in integration tests of external projects
100 changes: 100 additions & 0 deletions pytest-saas/exasol/pytest_saas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
from pathlib import Path

import pytest
from exasol.saas.client import openapi
from exasol.saas.client.api_access import (
OpenApiAccess,
create_saas_client,
timestamp_name,
)

import exasol.pytest_saas.project_short_tag as pst


def pytest_addoption(parser):
parser.addoption(
f"--saas-database-id",
help="""ID of the instance of an existing SaaS database to be
used during the current pytest session instead of creating a
dedicated instance temporarily.""",
)
parser.addoption(
f"--keep-saas-database",
action="store_true",
default=False,
help="""Keep the SaaS database instance created for the current
pytest session for subsequent inspection or reuse.""",
)
parser.addoption(
f"--project-short-tag",
help="""Short tag aka. "abbreviation" for your current project.
See docstring in project_short_tag.py for more details.
pytest plugin for exasol-saas-api will include this short tag into
the names of created database instances.""",
)


def _env(var: str) -> str:
result = os.environ.get(var)
if result:
return result
raise RuntimeError(f"Environment variable {var} is empty.")


@pytest.fixture(scope="session")
def saas_host() -> str:
return _env("SAAS_HOST")


@pytest.fixture(scope="session")
def saas_pat() -> str:
return _env("SAAS_PAT")


@pytest.fixture(scope="session")
def saas_account_id() -> str:
return _env("SAAS_ACCOUNT_ID")


@pytest.fixture(scope="session")
def project_short_tag(request):
return (
request.config.getoption("--project-short-tag")
or os.environ.get("PROJECT_SHORT_TAG")
or pst.read_from_yaml(request.config.rootpath)
)


@pytest.fixture(scope="session")
def database_name(project_short_tag):
return timestamp_name(project_short_tag)


@pytest.fixture(scope="session")
def api_access(saas_host, saas_pat, saas_account_id) -> OpenApiAccess:
with create_saas_client(saas_host, saas_pat) as client:
yield OpenApiAccess(client, saas_account_id)


@pytest.fixture(scope="session")
def saas_database(
request, api_access, database_name
) -> openapi.models.database.Database:
"""
Note: The SaaS instance database returned by this fixture initially
will not be operational. The startup takes about 20 minutes.
"""
db_id = request.config.getoption("--saas-database-id")
if db_id:
yield api_access.get_database(db_id)
return
with api_access.database(database_name) as db:
yield db


@pytest.fixture(scope="session")
def operational_saas_database_id(api_access, saas_database) -> str:
db = saas_database
api_access.wait_until_running(db.id)
return db.id
30 changes: 30 additions & 0 deletions pytest-saas/exasol/pytest_saas/project_short_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
A "Project Short Tag" is a short abbreviation for a project.

The pytest plugin for exasol-saas-api will include this short tag into the
names of created database instances to enable identifying the origin of
potentially long-running database instances in order to avoid unwanted costs.
"""

from pathlib import Path

FILE = "error_code_config.yml"


def read_from_yaml(dir: Path) -> str:
"""
Read project-short-tag from yaml file ``FILE`` in the specified
directory ``dir``.
"""
config_file = dir / FILE
if not config_file.exists():
return None
content = config_file.read_text()
header = False
for line in content.splitlines():
ckunki marked this conversation as resolved.
Show resolved Hide resolved
line = line.strip()
if header:
return line.strip().replace(":", "")
if line.startswith("error-tags:"):
header = True
raise RuntimeError(f"Could not read project short tag from file {config_file}")
116 changes: 116 additions & 0 deletions pytest-saas/test/integration/pytest_saas_test.py
ckunki marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import os
import re
from inspect import cleandoc
from unittest import mock

import pytest

pytest_plugins = "pytester"


@pytest.fixture
def make_test_files():
def make(pytester, files):
pytester.makepyfile(**files)

return make


def _testfile(body):
testname = re.sub(r"^.*def ([^(]+).*", "\\1", body, flags=re.S)
return { testname: cleandoc(body) }


def _cli_args(*args):
return args


def _env(**kwargs):
return kwargs


@pytest.mark.parametrize(
"files,cli_args",
[
( _testfile("""
def test_no_cli_args(request):
assert not request.config.getoption("--keep-saas-database")
assert request.config.getoption("--saas-database-id") is None
"""),
_cli_args(),
),
( _testfile("""
import os
def test_cli_args(request):
assert request.config.getoption("--keep-saas-database")
assert "123" == request.config.getoption("--saas-database-id")
assert "PST" == request.config.getoption("--project-short-tag")
"""),
_cli_args(
"--keep-saas-database",
"--project-short-tag", "PST",
"--saas-database-id", "123",
),
),
ckunki marked this conversation as resolved.
Show resolved Hide resolved
])
def test_pass_options_via_cli(pytester, make_test_files, files, cli_args):
make_test_files(pytester, files)
result = pytester.runpytest(*cli_args)
assert result.ret == pytest.ExitCode.OK


@pytest.mark.parametrize(
"pst_file, pst_env, pst_cli, expected",
[
("F", None, None, "F"),
("F", "E", None, "E"),
("F", "E", "C", "C"),
])
def test_project_short_tag(
request,
pytester,
pst_file,
pst_env,
pst_cli,
expected,
):
"""
This test sets different values for project short tag in file
error_code_config.yml, cli option --project-short-tag, and environment
variable PROJECT_SHORT_TAG and verifies the precedence.
"""
if pst_file:
pytester.makefile(".yml", **{
"error_code_config":
cleandoc(f"""
error-tags:
{pst_file}:
highest-index: 0
""")
})
pytester.makepyfile(** _testfile(
f"""
def test_project_short_tag(project_short_tag):
assert "{expected}" == project_short_tag
"""))
env = { "PROJECT_SHORT_TAG": pst_env } if pst_env else {}
cli_args = [ "--project-short-tag", pst_cli ] if pst_cli else []
with mock.patch.dict(os.environ, env):
result = pytester.runpytest(*cli_args)
assert result.ret == pytest.ExitCode.OK


def test_id_of_existing_database(request, pytester, capsys):
"""
Use an invalid ID and verify that exasol-saas-api signals an error
because that there is no database with the specified ID.
"""
testname = request.node.name
pytester.makepyfile(** _testfile( f"""
def {testname}(saas_database):
pass
"""))
result = pytester.runpytest("--saas-database-id", "123")
captured = capsys.readouterr()
assert result.ret != pytest.ExitCode.OK
assert "Database not found" in captured.out
2 changes: 0 additions & 2 deletions pytest-saas/test/integration/smoke_test.py

This file was deleted.

Loading