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

Make test suite run faster #115

Merged
merged 25 commits into from May 9, 2021
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6c7ad68
Avoid redundant queries in test suite
Flix6x Apr 29, 2021
4591349
Separate data source setup
Flix6x Apr 29, 2021
30b3831
Reduce the use of autouse=True
Flix6x Apr 29, 2021
1bcf16e
Less queries
Flix6x Apr 29, 2021
788c129
Typo
Flix6x Apr 29, 2021
a83ac8b
Fix test
Flix6x Apr 29, 2021
d512afc
Separate setup of inactive user
Flix6x Apr 29, 2021
fc08c60
Clean redis only in API and data package
Flix6x Apr 29, 2021
f7a8bff
Less setup of market prices
Flix6x Apr 29, 2021
81dfdf9
Add type annotations for return values
Flix6x Apr 29, 2021
2a8449b
Set up charging stations only when tests need them
Flix6x Apr 29, 2021
2622d28
Set up assets only when tests need them (stop autouse), and split off…
Flix6x May 4, 2021
e530550
Create draft PR for #114
Flix6x May 4, 2021
31f4613
Setup database with module scope
Flix6x May 4, 2021
407da74
Refactor test db setup at different scopes
Flix6x May 4, 2021
efd12c7
Merge branch 'speed-up-tests' into issue-114-Make_test_suite_run_faster
Flix6x May 4, 2021
71a947f
Refactor other main conftest setups at different scopes
Flix6x May 4, 2021
5edbf4f
Rename test modules using fresh db
Flix6x May 8, 2021
8dba854
Rename test modules using fresh db
Flix6x May 8, 2021
d8da752
Update developer documentation in main conftest
Flix6x May 8, 2021
4bf5462
Rename fixtures using fresh test db according to a single rule (appen…
Flix6x May 8, 2021
983d20b
Rename fresh_test_db to fresh_db
Flix6x May 8, 2021
8005ec0
Merge remote-tracking branch 'origin/main' into issue-114-Make_test_s…
Flix6x May 9, 2021
f931915
Changelog entry
Flix6x May 9, 2021
3acbd1c
Reformulate changelog entry
Flix6x May 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -18,6 +18,7 @@ Bugfixes
Infrastructure / Support
----------------------
* Make assets use MW as their default unit and enforce that in CLI, as well (API already did) [see `PR #108 <http://www.github.com/SeitaBV/flexmeasures/pull/108>`_]
* Recycle the test database to shave 2/3rd off of the time it takes for the FlexMeasures test suite to run [see `PR #115 <http://www.github.com/SeitaBV/flexmeasures/pull/115>`_]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say "Re-use the database between the automated tests, if possible. This shaves 2/3rd off the time ..."



v0.4.1 | May 7, 2021
Expand Down
4 changes: 2 additions & 2 deletions flexmeasures/api/tests/conftest.py
Expand Up @@ -7,8 +7,8 @@
from flask_security.utils import hash_password


@pytest.fixture(scope="function", autouse=True)
def setup_api_test_data(db):
@pytest.fixture(scope="module", autouse=True)
def setup_api_test_data(db, setup_roles_users):
"""
Adding the task-runner
"""
Expand Down
46 changes: 35 additions & 11 deletions flexmeasures/api/v1/tests/conftest.py
Expand Up @@ -4,27 +4,23 @@
import isodate
import pytest

from flask_security import SQLAlchemySessionUserDatastore
from flask_security.utils import hash_password

from flexmeasures.data.services.users import create_user


@pytest.fixture(scope="function", autouse=True)
def setup_api_test_data(db):
@pytest.fixture(scope="module", autouse=True)
def setup_api_test_data(db, setup_roles_users, add_market_prices):
"""
Set up data for API v1 tests.
"""
print("Setting up data for API v1 tests on %s" % db.engine)

from flexmeasures.data.models.user import User, Role
from flexmeasures.data.models.assets import Asset, AssetType, Power
from flexmeasures.data.models.data_sources import DataSource

user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)

# Create an anonymous user
create_user(
test_anonymous_prosumer = create_user(
username="anonymous user with Prosumer role",
email="demo@seita.nl",
password=hash_password("testtest"),
Expand All @@ -35,7 +31,6 @@ def setup_api_test_data(db):
)

# Create 1 test asset for the anonymous user
test_prosumer = user_datastore.find_user(email="demo@seita.nl")
test_asset_type = AssetType(name="test-type")
db.session.add(test_asset_type)
asset_names = ["CS 0"]
Expand All @@ -50,7 +45,7 @@ def setup_api_test_data(db):
longitude=100,
unit="MW",
)
asset.owner = test_prosumer
asset.owner = test_anonymous_prosumer
assets.append(asset)
db.session.add(asset)

Expand All @@ -62,7 +57,7 @@ def setup_api_test_data(db):
)

# Create 5 test assets for the test_prosumer user
test_prosumer = user_datastore.find_user(email="test_prosumer@seita.nl")
test_prosumer = setup_roles_users["Test Prosumer"]
asset_names = ["CS 1", "CS 2", "CS 3", "CS 4", "CS 5"]
assets: List[Asset] = []
for asset_name in asset_names:
Expand All @@ -83,7 +78,7 @@ def setup_api_test_data(db):

# Add power forecasts to one of the assets, for two sources
cs_5 = Asset.query.filter(Asset.name == "CS 5").one_or_none()
test_supplier = user_datastore.find_user(email="test_supplier@seita.nl")
test_supplier = setup_roles_users["Test Supplier"]
prosumer_data_source = DataSource.query.filter(
DataSource.user == test_prosumer
).one_or_none()
Expand Down Expand Up @@ -113,3 +108,32 @@ def setup_api_test_data(db):
db.session.bulk_save_objects(meter_data)

print("Done setting up data for API v1 tests")


@pytest.fixture(scope="function")
def setup_fresh_api_test_data(fresh_db, setup_roles_users_fresh_db):
db = fresh_db
setup_roles_users = setup_roles_users_fresh_db
from flexmeasures.data.models.assets import Asset, AssetType

# Create 5 test assets for the test_prosumer user
test_prosumer = setup_roles_users["Test Prosumer"]
test_asset_type = AssetType(name="test-type")
db.session.add(test_asset_type)
asset_names = ["CS 1", "CS 2", "CS 3", "CS 4", "CS 5"]
assets: List[Asset] = []
for asset_name in asset_names:
asset = Asset(
name=asset_name,
asset_type_name="test-type",
event_resolution=timedelta(minutes=15),
capacity_in_mw=1,
latitude=100,
longitude=100,
unit="MW",
)
asset.owner = test_prosumer
if asset_name == "CS 4":
asset.event_resolution = timedelta(hours=1)
assets.append(asset)
db.session.add(asset)
93 changes: 1 addition & 92 deletions flexmeasures/api/v1/tests/test_api_v1.py
Expand Up @@ -4,9 +4,6 @@
import isodate
import pandas as pd
import pytest
from iso8601 import parse_date
from numpy import repeat


from flexmeasures.api.common.responses import (
invalid_domain,
Expand All @@ -24,7 +21,6 @@
verify_power_in_db,
)
from flexmeasures.data.auth_setup import UNAUTH_ERROR_STATUS
from flexmeasures.api.v1.tests.utils import count_connections_in_post_message
from flexmeasures.data.models.assets import Asset


Expand Down Expand Up @@ -250,94 +246,7 @@ def test_get_meter_data(db, app, client, message):
assert get_meter_data_response.json["values"] == [(100.0 + i) for i in range(6)]


@pytest.mark.parametrize(
"post_message",
[
message_for_post_meter_data(),
message_for_post_meter_data(single_connection=True),
message_for_post_meter_data(single_connection_group=True),
],
)
@pytest.mark.parametrize(
"get_message",
[
message_for_get_meter_data(),
message_for_get_meter_data(single_connection=False),
message_for_get_meter_data(resolution="PT30M"),
],
)
def test_post_and_get_meter_data(db, app, client, post_message, get_message):
"""
Tries to post meter data as a logged-in test user with the MDC role, which should succeed.
There should be some ForecastingJobs waiting now.
Then tries to get meter data, which should succeed, and should return the same meter data as was posted,
or a downsampled version, if that was requested.
"""

# post meter data
auth_token = get_auth_token(client, "test_prosumer@seita.nl", "testtest")
post_meter_data_response = client.post(
url_for("flexmeasures_api_v1.post_meter_data"),
json=message_replace_name_with_ea(post_message),
headers={"Authorization": auth_token},
)
print("Server responded with:\n%s" % post_meter_data_response.json)
assert post_meter_data_response.status_code == 200
assert post_meter_data_response.json["type"] == "PostMeterDataResponse"

# look for Forecasting jobs
expected_connections = count_connections_in_post_message(post_message)
assert (
len(app.queues["forecasting"]) == 4 * expected_connections
) # four horizons times the number of assets
horizons = repeat(
[
timedelta(hours=1),
timedelta(hours=6),
timedelta(hours=24),
timedelta(hours=48),
],
expected_connections,
)
jobs = sorted(app.queues["forecasting"].jobs, key=lambda x: x.kwargs["horizon"])
for job, horizon in zip(jobs, horizons):
assert job.kwargs["horizon"] == horizon
assert job.kwargs["start"] == parse_date(post_message["start"]) + horizon
for asset_name in ("CS 1", "CS 2", "CS 3"):
if asset_name in str(post_message):
asset = Asset.query.filter_by(name=asset_name).one_or_none()
assert asset.id in [job.kwargs["asset_id"] for job in jobs]

# get meter data
get_meter_data_response = client.get(
url_for("flexmeasures_api_v1.get_meter_data"),
query_string=message_replace_name_with_ea(get_message),
headers={"Authorization": auth_token},
)
print("Server responded with:\n%s" % get_meter_data_response.json)
assert get_meter_data_response.status_code == 200
assert get_meter_data_response.json["type"] == "GetMeterDataResponse"
if "groups" in post_message:
posted_values = post_message["groups"][0]["values"]
else:
posted_values = post_message["values"]
if "groups" in get_meter_data_response.json:
gotten_values = get_meter_data_response.json["groups"][0]["values"]
else:
gotten_values = get_meter_data_response.json["values"]

if "resolution" not in get_message or get_message["resolution"] == "":
assert gotten_values == posted_values
else:
# We used a target resolution of 30 minutes, so double of 15 minutes.
# Six values went in, three come out.
if posted_values[1] > 0: # see utils.py:message_for_post_meter_data
assert gotten_values == [306.66, -0.0, 306.66]
else:
assert gotten_values == [153.33, 0, 306.66]


def test_post_meter_data_to_different_resolutions(db, app, client):
def test_post_meter_data_to_different_resolutions(app, client):
"""
Tries to post meter data to assets with different event_resolutions, which is not accepted.
"""
Expand Down
104 changes: 104 additions & 0 deletions flexmeasures/api/v1/tests/test_api_v1_fresh_db.py
@@ -0,0 +1,104 @@
from datetime import timedelta

import pytest
from flask import url_for
from iso8601 import parse_date
from numpy import repeat

from flexmeasures.api.common.utils.api_utils import message_replace_name_with_ea
from flexmeasures.api.tests.utils import get_auth_token
from flexmeasures.api.v1.tests.utils import (
message_for_post_meter_data,
message_for_get_meter_data,
count_connections_in_post_message,
)
from flexmeasures.data.models.assets import Asset


@pytest.mark.parametrize(
"post_message",
[
message_for_post_meter_data(),
message_for_post_meter_data(single_connection=True),
message_for_post_meter_data(single_connection_group=True),
],
)
@pytest.mark.parametrize(
"get_message",
[
message_for_get_meter_data(),
message_for_get_meter_data(single_connection=False),
message_for_get_meter_data(resolution="PT30M"),
],
)
def test_post_and_get_meter_data(
setup_fresh_api_test_data, app, clean_redis, client, post_message, get_message
):
"""
Tries to post meter data as a logged-in test user with the MDC role, which should succeed.
There should be some ForecastingJobs waiting now.
Then tries to get meter data, which should succeed, and should return the same meter data as was posted,
or a downsampled version, if that was requested.
"""

# post meter data
auth_token = get_auth_token(client, "test_prosumer@seita.nl", "testtest")
post_meter_data_response = client.post(
url_for("flexmeasures_api_v1.post_meter_data"),
json=message_replace_name_with_ea(post_message),
headers={"Authorization": auth_token},
)
print("Server responded with:\n%s" % post_meter_data_response.json)
assert post_meter_data_response.status_code == 200
assert post_meter_data_response.json["type"] == "PostMeterDataResponse"

# look for Forecasting jobs
expected_connections = count_connections_in_post_message(post_message)
assert (
len(app.queues["forecasting"]) == 4 * expected_connections
) # four horizons times the number of assets
horizons = repeat(
[
timedelta(hours=1),
timedelta(hours=6),
timedelta(hours=24),
timedelta(hours=48),
],
expected_connections,
)
jobs = sorted(app.queues["forecasting"].jobs, key=lambda x: x.kwargs["horizon"])
for job, horizon in zip(jobs, horizons):
assert job.kwargs["horizon"] == horizon
assert job.kwargs["start"] == parse_date(post_message["start"]) + horizon
for asset_name in ("CS 1", "CS 2", "CS 3"):
if asset_name in str(post_message):
asset = Asset.query.filter_by(name=asset_name).one_or_none()
assert asset.id in [job.kwargs["asset_id"] for job in jobs]

# get meter data
get_meter_data_response = client.get(
url_for("flexmeasures_api_v1.get_meter_data"),
query_string=message_replace_name_with_ea(get_message),
headers={"Authorization": auth_token},
)
print("Server responded with:\n%s" % get_meter_data_response.json)
assert get_meter_data_response.status_code == 200
assert get_meter_data_response.json["type"] == "GetMeterDataResponse"
if "groups" in post_message:
posted_values = post_message["groups"][0]["values"]
else:
posted_values = post_message["values"]
if "groups" in get_meter_data_response.json:
gotten_values = get_meter_data_response.json["groups"][0]["values"]
else:
gotten_values = get_meter_data_response.json["values"]

if "resolution" not in get_message or get_message["resolution"] == "":
assert gotten_values == posted_values
else:
# We used a target resolution of 30 minutes, so double of 15 minutes.
# Six values went in, three come out.
if posted_values[1] > 0: # see utils.py:message_for_post_meter_data
assert gotten_values == [306.66, -0.0, 306.66]
else:
assert gotten_values == [153.33, 0, 306.66]
13 changes: 10 additions & 3 deletions flexmeasures/api/v1_1/tests/conftest.py
Expand Up @@ -11,8 +11,8 @@
from flexmeasures.data.services.users import create_user


@pytest.fixture(scope="function", autouse=True)
def setup_api_test_data(db):
@pytest.fixture(scope="module")
def setup_api_test_data(db, setup_roles_users, add_market_prices):
"""
Set up data for API v1.1 tests.
"""
Expand Down Expand Up @@ -41,7 +41,7 @@ def setup_api_test_data(db):
)

# Create 3 test assets for the test_prosumer user
test_prosumer = user_datastore.find_user(email="test_prosumer@seita.nl")
test_prosumer = setup_roles_users["Test Prosumer"]
test_asset_type = AssetType(name="test-type")
db.session.add(test_asset_type)
asset_names = ["CS 1", "CS 2", "CS 3"]
Expand Down Expand Up @@ -124,3 +124,10 @@ def setup_api_test_data(db):
db.session.add(sensor)

print("Done setting up data for API v1.1 tests")


@pytest.fixture(scope="function")
def setup_fresh_api_v1_1_test_data(
fresh_db, setup_roles_users_fresh_db, setup_markets_fresh_db
):
return fresh_db