Exemple #1
0
def test_transaction(caplog: _pytest.logging.LogCaptureFixture,
                     logger: gluetool.log.ContextAdapter, db: DB) -> None:
    """
    Test whether :py:func:`transaction` behaves correctly when wrapping non-conflicting queries.
    """

    with db.get_session(transactional=True) as session:
        update = tft.artemis.tasks._guest_state_update_query(
            'dummy-guest',
            GuestState.PROVISIONING,
            current_state=GuestState.ROUTING).unwrap()

        insert = sqlalchemy.insert(
            GuestEvent.__table__).values(  # type: ignore[attr-defined]
                updated=datetime.datetime.utcnow(),
                guestname='dummy-guest',
                eventname='dummy-event')

        with transaction() as r:
            session.execute(update)
            session.execute(insert)

        assert r.success is True

    requests = SafeQuery.from_session(session, GuestRequest).all().unwrap()

    assert len(requests) == 1
    # TODO: cast shouldn't be needed, sqlalchemy should annouce .state as enum - maybe with more recent stubs?
    assert cast(GuestState, requests[0].state) == GuestState.PROVISIONING

    events = SafeQuery.from_session(session, GuestEvent).all().unwrap()

    assert len(events) == 1
    assert events[0].guestname == 'dummy-guest'
    assert events[0].eventname == 'dummy-event'
Exemple #2
0
def test_session_autocommit_active_only(db: DB,
                                        mock_session: MagicMock) -> None:
    mock_session.transaction.is_active = False

    with db.get_session():
        pass

    mock_session.commit.assert_not_called()
    mock_session.close.assert_called_once()
Exemple #3
0
def test_run_doer_exception(logger: gluetool.log.ContextAdapter,
                            db: tft.artemis.db.DB,
                            cancel: threading.Event) -> None:
    def foo(_logger: gluetool.log.ContextAdapter, _db: tft.artemis.db.DB,
            _session: sqlalchemy.orm.session.Session,
            _cancel: threading.Event) -> tft.artemis.tasks.DoerReturnType:
        raise Exception('foo')

    with db.get_session() as session:
        with pytest.raises(Exception, match=r'foo'):
            tft.artemis.tasks.run_doer(logger, db, session, cancel,
                                       cast(tft.artemis.tasks.DoerType, foo),
                                       'test_run_doer_exception')
Exemple #4
0
def test_run_doer(logger: gluetool.log.ContextAdapter, db: tft.artemis.db.DB,
                  cancel: threading.Event) -> None:
    def foo(_logger: gluetool.log.ContextAdapter, _db: tft.artemis.db.DB,
            _session: sqlalchemy.orm.session.Session, _cancel: threading.Event,
            bar: str) -> tft.artemis.tasks.DoerReturnType:
        assert bar == '79'

        return tft.artemis.tasks.RESCHEDULE

    with db.get_session() as session:
        assert tft.artemis.tasks.run_doer(
            logger, db, session, cancel, cast(tft.artemis.tasks.DoerType, foo),
            'test_run_doer', '79') == tft.artemis.tasks.RESCHEDULE
Exemple #5
0
def test_session_autorollback(db: DB, mock_session: MagicMock) -> None:
    mock_exception = ValueError('Exception<mock>')

    try:
        with db.get_session():
            raise mock_exception

    except Exception as exc:
        assert exc is mock_exception

    mock_session.commit.assert_not_called()
    mock_session.rollback.assert_called_once()
    mock_session.close.assert_called_once()
Exemple #6
0
def test_session_autocommit(db: DB, mock_session: MagicMock) -> None:
    with db.get_session() as session:
        assert session is mock_session

    mock_session.commit.assert_called_once()
    mock_session.close.assert_called_once()
Exemple #7
0
def test_session(db: DB) -> None:
    with db.get_session() as session:
        assert hasattr(session, 'commit')
Exemple #8
0
def test_transaction_conflict(caplog: _pytest.logging.LogCaptureFixture,
                              logger: gluetool.log.ContextAdapter, db: DB,
                              session: sqlalchemy.orm.session.Session) -> None:
    """
    Test whether :py:func:`transaction` intercepts and reports transaction rollback.
    """

    with db.get_session(transactional=True) as session2, db.get_session(
            transactional=True) as session3:
        update1 = tft.artemis.tasks._guest_state_update_query(
            'dummy-guest',
            GuestState.PROVISIONING,
            current_state=GuestState.ROUTING).unwrap()

        update2 = tft.artemis.tasks._guest_state_update_query(
            'dummy-guest',
            GuestState.PROMISED,
            current_state=GuestState.ROUTING).unwrap()

        insert1 = sqlalchemy.insert(
            GuestEvent.__table__).values(  # type: ignore[attr-defined]
                updated=datetime.datetime.utcnow(),
                guestname='dummy-guest',
                eventname='dummy-event')

        insert2 = sqlalchemy.insert(
            GuestEvent.__table__).values(  # type: ignore[attr-defined]
                updated=datetime.datetime.utcnow(),
                guestname='dummy-guest',
                eventname='another-dummy-event')

        # To create conflict, we must "initialize" view of both sessions, by executing a query. This will setup
        # their initial knowledge - without this step, the second transaction wouldn't run into any conflict because
        # it would issue its first query when the first transaction has been already committed.
        #
        # Imagine two tasks, both loading guest request from DB, then making some decisions, eventually both
        # trying to change it. The initial DB query sets the stage for both transactions seeing the same DB
        # state, and only one is allowed to modify the records both touched.
        SafeQuery.from_session(session2, GuestRequest).all()
        SafeQuery.from_session(session3, GuestRequest).all()

        with transaction() as r1:
            session2.execute(update1)
            session2.execute(insert1)

        session2.commit(
        )  # type: ignore[no-untyped-call]  # TODO: untyped commit()??

        assert r1.success is True

        with transaction() as r2:
            session3.execute(update2)
            session3.execute(insert2)

        session2.commit(
        )  # type: ignore[no-untyped-call]  # TODO: untyped commit()??

        assert r2.success is False

    requests = SafeQuery.from_session(session, GuestRequest).all().unwrap()

    assert len(requests) == 1
    # TODO: cast shouldn't be needed, sqlalchemy should annouce .state as enum - maybe with more recent stubs?
    assert cast(GuestState, requests[0].state) == GuestState.PROVISIONING

    events = SafeQuery.from_session(session, GuestEvent).all().unwrap()

    assert len(events) == 1
    assert events[0].guestname == 'dummy-guest'
    assert events[0].eventname == 'dummy-event'