mirror of
https://github.com/apache/superset.git
synced 2026-07-11 17:25:31 +00:00
80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
from click.testing import CliRunner
|
|
from pytest_mock import MockerFixture
|
|
|
|
from superset.cli import thumbnails
|
|
|
|
|
|
class _FakeModel:
|
|
"""Stand-in for a Dashboard/Slice that detaches mid-run.
|
|
|
|
Once ``detached`` is set, ``str()`` raises -- mimicking the
|
|
DetachedInstanceError that SQLAlchemy raises when the session that
|
|
loaded the instance has been closed/expired.
|
|
"""
|
|
|
|
def __init__(self, pk: int, label: str) -> None:
|
|
self.id: int = pk
|
|
self._label: str = label
|
|
self.detached: bool = False
|
|
|
|
def __str__(self) -> str:
|
|
if self.detached:
|
|
raise RuntimeError("Instance is not bound to a Session (simulated)")
|
|
return self._label
|
|
|
|
|
|
def test_compute_thumbnails_survives_detached_instances(
|
|
mocker: MockerFixture, app_context: None
|
|
) -> None:
|
|
"""Regression test for the DetachedInstanceError in compute-thumbnails.
|
|
|
|
Computing a thumbnail can close/expire the session, detaching the ORM
|
|
instances. The command must read each model's id and label *before* the
|
|
compute loop, so building the progress message never touches a detached
|
|
instance even when several models are processed.
|
|
"""
|
|
models = [_FakeModel(1, "Dashboard A"), _FakeModel(2, "Dashboard B")]
|
|
|
|
query = mocker.MagicMock()
|
|
query.filter.return_value = query
|
|
query.all.return_value = models
|
|
db_mock = mocker.patch("superset.cli.thumbnails.db")
|
|
db_mock.session.query.return_value = query
|
|
|
|
def _detach_session(_url: object, _model_id: int, force: bool) -> None:
|
|
# Computing a thumbnail expires the session -> every instance detaches.
|
|
for model in models:
|
|
model.detached = True
|
|
|
|
cache_dashboard = mocker.patch(
|
|
"superset.tasks.thumbnails.cache_dashboard_thumbnail",
|
|
side_effect=_detach_session,
|
|
)
|
|
|
|
result = CliRunner().invoke(thumbnails.compute_thumbnails, ["-d"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
# Both dashboards were processed despite the simulated detachment...
|
|
assert cache_dashboard.call_count == 2
|
|
cache_dashboard.assert_any_call(None, 1, force=False)
|
|
cache_dashboard.assert_any_call(None, 2, force=False)
|
|
# ...and their labels were captured up front for the progress output.
|
|
assert "Dashboard A" in result.output
|
|
assert "Dashboard B" in result.output
|