Skip to content

Commit

Permalink
wtf: Improve test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Jun 4, 2024
1 parent 49d1a3f commit 3de21e2
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 19 deletions.
21 changes: 14 additions & 7 deletions cratedb_toolkit/sqlalchemy/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
from decimal import Decimal
from uuid import UUID

import numpy as np
import sqlalchemy as sa

try:
import numpy as np

has_numpy = True
except ImportError:
has_numpy = False

Check warning on line 15 in cratedb_toolkit/sqlalchemy/patch.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/sqlalchemy/patch.py#L14-L15

Added lines #L14 - L15 were not covered by tests


def patch_inspector():
"""
Expand Down Expand Up @@ -65,10 +71,11 @@ def default(self, o):

# NumPy ndarray and friends.
# https://stackoverflow.com/a/49677241
if isinstance(o, np.integer):
return int(o)
elif isinstance(o, np.floating):
return float(o)
elif isinstance(o, np.ndarray):
return o.tolist()
if has_numpy:
if isinstance(o, np.integer):
return int(o)
elif isinstance(o, np.floating):
return float(o)
elif isinstance(o, np.ndarray):
return o.tolist()
return json.JSONEncoder.default(self, o)

Check warning on line 81 in cratedb_toolkit/sqlalchemy/patch.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/sqlalchemy/patch.py#L74-L81

Added lines #L74 - L81 were not covered by tests
17 changes: 13 additions & 4 deletions cratedb_toolkit/wtf/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,21 @@ def job_statistics(ctx: click.Context):


@make_command(job_statistics, "collect", "Collect queries from sys.jobs_log.")
@click.option("--once", is_flag=True, default=False, required=False, help="Whether to record only one sample")
@click.pass_context
def job_statistics_collect(ctx: click.Context):
def job_statistics_collect(ctx: click.Context, once: bool):
"""
Run jobs_log collector.
# TODO: Forward `cratedb_sqlalchemy_url` properly.
"""
import cratedb_toolkit.wtf.query_collector

Check warning on line 155 in cratedb_toolkit/wtf/cli.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/cli.py#L155

Added line #L155 was not covered by tests

cratedb_toolkit.wtf.query_collector.main()
cratedb_toolkit.wtf.query_collector.init()
if once:
cratedb_toolkit.wtf.query_collector.record_once()

Check warning on line 159 in cratedb_toolkit/wtf/cli.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/cli.py#L157-L159

Added lines #L157 - L159 were not covered by tests
else:
cratedb_toolkit.wtf.query_collector.record_forever()

Check warning on line 161 in cratedb_toolkit/wtf/cli.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/cli.py#L161

Added line #L161 was not covered by tests


@make_command(job_statistics, "view", "View job statistics about collected queries.")
Expand All @@ -180,13 +185,17 @@ def job_statistics_view(ctx: click.Context):


@make_command(cli, "record", "Record `info` and `job-info` outcomes.")
@click.option("--once", is_flag=True, default=False, required=False, help="Whether to record only one sample")
@click.pass_context
def record(ctx: click.Context):
def record(ctx: click.Context, once: bool):
cratedb_sqlalchemy_url = ctx.meta["cratedb_sqlalchemy_url"]
scrub = ctx.meta.get("scrub", False)
adapter = DatabaseAdapter(dburi=cratedb_sqlalchemy_url, echo=False)
recorder = InfoRecorder(adapter=adapter, scrub=scrub)
recorder.record_forever()
if once:
recorder.record_once()

Check warning on line 196 in cratedb_toolkit/wtf/cli.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/cli.py#L191-L196

Added lines #L191 - L196 were not covered by tests
else:
recorder.record_forever()

Check warning on line 198 in cratedb_toolkit/wtf/cli.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/cli.py#L198

Added line #L198 was not covered by tests


@make_command(cli, "serve", help_serve)
Expand Down
2 changes: 1 addition & 1 deletion cratedb_toolkit/wtf/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Distributed under the terms of the AGPLv3 license, see LICENSE.
import logging
import os
import typing as t
from functools import lru_cache

Check warning on line 5 in cratedb_toolkit/wtf/http.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/http.py#L3-L5

Added lines #L3 - L5 were not covered by tests

import typing_extensions as t
from fastapi import Depends, FastAPI, HTTPException

Check warning on line 8 in cratedb_toolkit/wtf/http.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/http.py#L7-L8

Added lines #L7 - L8 were not covered by tests

from cratedb_toolkit.util import DatabaseAdapter
Expand Down
15 changes: 10 additions & 5 deletions cratedb_toolkit/wtf/query_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def init_stmts(stmts):


def write_stats_to_db():
logger.info("Writing statistics to database")
logger.info(f"Writing statistics to database table: {stmt_log_table}")
write_query_stmt = (

Check warning on line 106 in cratedb_toolkit/wtf/query_collector.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/query_collector.py#L104-L106

Added lines #L104 - L106 were not covered by tests
f"INSERT INTO {stmt_log_table} "
f"(id, stmt, calls, bucket, username, query_type, avg_duration, nodes, last_used) "
Expand Down Expand Up @@ -230,18 +230,23 @@ def scrape_db():
last_scrape = next_scrape

Check warning on line 230 in cratedb_toolkit/wtf/query_collector.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/query_collector.py#L227-L230

Added lines #L227 - L230 were not covered by tests


def run():
def record_once():
logger.info("Recording information snapshot")
scrape_db()
write_stats_to_db()

Check warning on line 236 in cratedb_toolkit/wtf/query_collector.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/query_collector.py#L233-L236

Added lines #L233 - L236 were not covered by tests


def main():
init()
def record_forever():
while True:
run()
record_once()
logger.info(f"Sleeping for {interval} seconds")
time.sleep(interval)

Check warning on line 243 in cratedb_toolkit/wtf/query_collector.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/query_collector.py#L239-L243

Added lines #L239 - L243 were not covered by tests


def main():
init()
record_forever()

Check warning on line 248 in cratedb_toolkit/wtf/query_collector.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/query_collector.py#L246-L248

Added lines #L246 - L248 were not covered by tests


if __name__ == "__main__":
main()

Check warning on line 252 in cratedb_toolkit/wtf/query_collector.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/query_collector.py#L251-L252

Added lines #L251 - L252 were not covered by tests
1 change: 1 addition & 0 deletions cratedb_toolkit/wtf/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ def do_record_forever(self):
self.record_once()
except Exception:
logger.exception("Failed to record information snapshot")
logger.info(f"Sleeping for {self.interval_seconds} seconds")
time.sleep(self.interval_seconds)

Check warning on line 59 in cratedb_toolkit/wtf/recorder.py

View check run for this annotation

Codecov / codecov/patch

cratedb_toolkit/wtf/recorder.py#L53-L59

Added lines #L53 - L59 were not covered by tests
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ service = [
]
test = [
"cratedb-toolkit[testing]",
"httpx<0.28",
"pueblo[dataframe]",
"pytest<9",
"pytest-cov<6",
Expand Down Expand Up @@ -288,6 +289,7 @@ extend-exclude = [
[tool.ruff.lint.per-file-ignores]
"doc/conf.py" = ["A001", "ERA001"]
"tests/*" = ["S101"] # Allow use of `assert`, and `print`.
"tests/wtf/test_http.py" = ["E402"]
"examples/*" = ["T201"] # Allow `print`
"cratedb_toolkit/retention/cli.py" = ["T201"] # Allow `print`
"cratedb_toolkit/sqlalchemy/__init__.py" = ["F401"] # Allow `module´ imported but unused
Expand Down
42 changes: 40 additions & 2 deletions tests/cfr/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def filenames(path: Path):
return sorted([item.name for item in path.iterdir()])


def test_cfr_cli_export(cratedb, tmp_path, caplog):
def test_cfr_cli_export_success(cratedb, tmp_path, caplog):
"""
Verify `ctk cfr sys-export` works.
"""
Expand Down Expand Up @@ -49,7 +49,26 @@ def test_cfr_cli_export(cratedb, tmp_path, caplog):
assert len(data_files) >= 10


def test_cfr_cli_import(cratedb, tmp_path, caplog):
def test_cfr_cli_export_failure(cratedb, tmp_path, caplog):
"""
Verify `ctk cfr sys-export` failure.
"""

# Invoke command.
runner = CliRunner(env={"CRATEDB_SQLALCHEMY_URL": "crate://foo.bar/", "CFR_TARGET": str(tmp_path)})
result = runner.invoke(
cli,
args="--debug sys-export",
catch_exceptions=False,
)
assert result.exit_code == 1

# Verify log output.
assert "Failed to establish a new connection" in caplog.text or "Failed to resolve" in caplog.text
assert result.output == ""


def test_cfr_cli_import_success(cratedb, tmp_path, caplog):
"""
Verify `ctk cfr sys-import` works.
"""
Expand Down Expand Up @@ -108,3 +127,22 @@ def test_cfr_cli_import(cratedb, tmp_path, caplog):

cratedb.database.run_sql('REFRESH TABLE "sys-operations"')
assert cratedb.database.count_records("sys-operations") == 1


def test_cfr_cli_import_failure(cratedb, tmp_path, caplog):
"""
Verify `ctk cfr sys-import` failure.
"""

# Invoke command.
runner = CliRunner(env={"CRATEDB_SQLALCHEMY_URL": "crate://foo.bar/", "CFR_SOURCE": str(tmp_path)})
result = runner.invoke(
cli,
args="--debug sys-import",
catch_exceptions=False,
)
assert result.exit_code == 1

# Verify log output.
assert "Failed to establish a new connection" in caplog.text or "Failed to resolve" in caplog.text
assert result.output == ""
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
TESTDRIVE_DATA_SCHEMA = "testdrive-data"
TESTDRIVE_EXT_SCHEMA = "testdrive-ext"
RESET_TABLES = [
# FIXME: Let all subsystems use configured schema instead of hard-coded ones.
'"doc"."clusterinfo"',
'"doc"."jobinfo"',
'"ext"."clusterinfo"',
'"ext"."jobinfo"',
'"stats"."statement_log"',
'"stats"."last_execution"',
f'"{TESTDRIVE_EXT_SCHEMA}"."retention_policy"',
f'"{TESTDRIVE_DATA_SCHEMA}"."raw_metrics"',
f'"{TESTDRIVE_DATA_SCHEMA}"."sensor_readings"',
Expand Down
25 changes: 25 additions & 0 deletions tests/sqlalchemy/test_patch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import datetime
import json

import pytest
import sqlalchemy as sa

from cratedb_toolkit.sqlalchemy import patch_inspector
from cratedb_toolkit.sqlalchemy.patch import CrateJsonEncoderWithNumPy
from tests.conftest import TESTDRIVE_DATA_SCHEMA


Expand Down Expand Up @@ -40,3 +45,23 @@ def test_inspector_patched(database):

table_names = inspector.get_table_names()
assert "foobar" in table_names


def test_json_encoder_date():
"""
Verify the extended JSON encoder also accepts Python's `date` types.
"""
data = {"date": datetime.date(2024, 6, 4)}
encoded = json.dumps(data, cls=CrateJsonEncoderWithNumPy)
assert encoded == '{"date": 1717459200000}'


def test_json_encoder_numpy():
"""
Verify the extended JSON encoder also accepts NumPy types.
"""
np = pytest.importorskip("numpy")

data = {"scalar-int": np.float32(42.42).astype(int), "scalar-float": np.float32(42.42), "ndarray": np.ndarray([1])}
encoded = json.dumps(data, cls=CrateJsonEncoderWithNumPy)
assert encoded == """{"scalar-int": 42, "scalar-float": 42.41999816894531, "ndarray": [2.08e-322]}"""
Empty file added tests/util/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions tests/util/test_platform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from cratedb_toolkit.util.platform import PlatformInfo


def test_platforminfo_application():
pi = PlatformInfo()
outcome = pi.application()
assert "name" in outcome
assert "version" in outcome
assert "platform" in outcome


def test_platforminfo_libraries():
pi = PlatformInfo()
outcome = pi.libraries()
assert isinstance(outcome, dict)
64 changes: 64 additions & 0 deletions tests/wtf/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from boltons.iterutils import get_path
from click.testing import CliRunner
from yarl import URL

from cratedb_toolkit.wtf.cli import cli

Expand Down Expand Up @@ -88,6 +89,40 @@ def test_wtf_cli_job_info(cratedb):
assert "performance15min" in data_keys


def test_wtf_cli_statistics_collect(cratedb, caplog):
"""
Verify `cratedb-wtf job-statistics collect`.
"""

uri = URL(cratedb.database.dburi)

# Invoke command.
runner = CliRunner(env={"CRATEDB_SQLALCHEMY_URL": cratedb.database.dburi})
result = runner.invoke(
cli,
args="job-statistics collect --once",
env={"HOSTNAME": f"{uri.host}:{uri.port}"},
catch_exceptions=False,
)
assert result.exit_code == 0

# Verify outcome: Log output.
assert "Recording information snapshot" in caplog.messages

# Verify outcome: Database content.
# stats.statement_log, stats.last_execution
results = cratedb.database.run_sql("SHOW TABLES", records=True)
assert {"table_name": "last_execution"} in results
assert {"table_name": "statement_log"} in results

# FIXME: Table is empty. Why?
cratedb.database.run_sql('REFRESH TABLE "stats"."statement_log"')
assert cratedb.database.count_records("stats.statement_log") == 0

cratedb.database.run_sql('REFRESH TABLE "stats"."last_execution"')
assert cratedb.database.count_records("stats.last_execution") == 1


def test_wtf_cli_statistics_view(cratedb):
"""
Verify `cratedb-wtf job-statistics view`.
Expand All @@ -109,3 +144,32 @@ def test_wtf_cli_statistics_view(cratedb):

data_keys = list(info["data"].keys())
assert "stats" in data_keys


def test_wtf_cli_record(cratedb, caplog):
"""
Verify `cratedb-wtf record`.
"""

# Invoke command.
runner = CliRunner(env={"CRATEDB_SQLALCHEMY_URL": cratedb.database.dburi})
result = runner.invoke(
cli,
args="--debug record --once",
catch_exceptions=False,
)
assert result.exit_code == 0

# Verify outcome: Log output.
assert "Recording information snapshot" in caplog.messages

# Verify outcome: Database content.
results = cratedb.database.run_sql("SHOW TABLES", records=True)
assert {"table_name": "clusterinfo"} in results
assert {"table_name": "jobinfo"} in results

cratedb.database.run_sql('REFRESH TABLE "ext"."clusterinfo"')
assert cratedb.database.count_records("ext.clusterinfo") == 1

cratedb.database.run_sql('REFRESH TABLE "ext"."jobinfo"')
assert cratedb.database.count_records("ext.jobinfo") == 1
Loading

0 comments on commit 3de21e2

Please sign in to comment.