Skip to content

Commit

Permalink
refactor: move tests for 'Client.transaction' to new module
Browse files Browse the repository at this point in the history
Use pytest fixtures / idioms.

Toward #200.
  • Loading branch information
tseaver committed Aug 9, 2021
1 parent 89ab32e commit ad2023e
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 101 deletions.
3 changes: 2 additions & 1 deletion tests/system/conftest.py
Expand Up @@ -48,4 +48,5 @@ def entities_to_delete(datastore_client):

yield entities_to_delete

datastore_client.delete_multi(entities_to_delete)
with datastore_client.transaction():
datastore_client.delete_multi(entities_to_delete)
17 changes: 17 additions & 0 deletions tests/system/test_put.py
Expand Up @@ -20,6 +20,8 @@
from google.cloud import datastore
from google.cloud.datastore.helpers import GeoPoint

from . import _helpers


def parent_key(datastore_client):
return datastore_client.key("Blog", "PizzaMan")
Expand Down Expand Up @@ -144,3 +146,18 @@ def test_client_put_w_entity_w_self_reference(datastore_client, entities_to_dele

stored_persons = list(query.fetch(limit=2))
assert stored_persons == [entity]


def test_client_put_w_empty_array(datastore_client, entities_to_delete):
local_client = _helpers.clone_client(datastore_client)

key = local_client.key("EmptyArray", 1234)
local_client = datastore.Client()
entity = datastore.Entity(key=key)
entity["children"] = []
local_client.put(entity)
entities_to_delete.append(entity)

retrieved = local_client.get(entity.key)

assert entity["children"] == retrieved["children"]
100 changes: 0 additions & 100 deletions tests/system/test_system.py
Expand Up @@ -13,13 +13,11 @@
# limitations under the License.

import os
import unittest

import requests

from google.cloud import datastore
from google.cloud.datastore.client import DATASTORE_DATASET
from google.cloud.exceptions import Conflict

from test_utils.system import unique_resource_id

Expand Down Expand Up @@ -67,101 +65,3 @@ def setUpModule():
def tearDownModule():
with Config.CLIENT.transaction():
Config.CLIENT.delete_multi(Config.TO_DELETE)


class TestDatastore(unittest.TestCase):
def setUp(self):
self.case_entities_to_delete = []

def tearDown(self):
with Config.CLIENT.transaction():
Config.CLIENT.delete_multi(self.case_entities_to_delete)


class TestDatastoreTransaction(TestDatastore):
def test_transaction_via_with_statement(self):
entity = datastore.Entity(key=Config.CLIENT.key("Company", "Google"))
entity["url"] = u"www.google.com"

with Config.CLIENT.transaction() as xact:
result = Config.CLIENT.get(entity.key)
if result is None:
xact.put(entity)
self.case_entities_to_delete.append(entity)

# This will always return after the transaction.
retrieved_entity = Config.CLIENT.get(entity.key)
self.case_entities_to_delete.append(retrieved_entity)
self.assertEqual(retrieved_entity, entity)

def test_transaction_via_explicit_begin_get_commit(self):
# See
# github.com/GoogleCloudPlatform/google-cloud-python/issues/1859
# Note that this example lacks the threading which provokes the race
# condition in that issue: we are basically just exercising the
# "explict" path for using transactions.
BEFORE_1 = 100
BEFORE_2 = 0
TRANSFER_AMOUNT = 40
key1 = Config.CLIENT.key("account", "123")
account1 = datastore.Entity(key=key1)
account1["balance"] = BEFORE_1
key2 = Config.CLIENT.key("account", "234")
account2 = datastore.Entity(key=key2)
account2["balance"] = BEFORE_2
Config.CLIENT.put_multi([account1, account2])
self.case_entities_to_delete.append(account1)
self.case_entities_to_delete.append(account2)

xact = Config.CLIENT.transaction()
xact.begin()
from_account = Config.CLIENT.get(key1, transaction=xact)
to_account = Config.CLIENT.get(key2, transaction=xact)
from_account["balance"] -= TRANSFER_AMOUNT
to_account["balance"] += TRANSFER_AMOUNT

xact.put(from_account)
xact.put(to_account)
xact.commit()

after1 = Config.CLIENT.get(key1)
after2 = Config.CLIENT.get(key2)
self.assertEqual(after1["balance"], BEFORE_1 - TRANSFER_AMOUNT)
self.assertEqual(after2["balance"], BEFORE_2 + TRANSFER_AMOUNT)

def test_failure_with_contention(self):
contention_prop_name = "baz"
local_client = clone_client(Config.CLIENT)

# Insert an entity which will be retrieved in a transaction
# and updated outside it with a contentious value.
key = local_client.key("BreakTxn", 1234)
orig_entity = datastore.Entity(key=key)
orig_entity["foo"] = u"bar"
local_client.put(orig_entity)
self.case_entities_to_delete.append(orig_entity)

with self.assertRaises(Conflict):
with local_client.transaction() as txn:
entity_in_txn = local_client.get(key)

# Update the original entity outside the transaction.
orig_entity[contention_prop_name] = u"outside"
Config.CLIENT.put(orig_entity)

# Try to update the entity which we already updated outside the
# transaction.
entity_in_txn[contention_prop_name] = u"inside"
txn.put(entity_in_txn)

def test_empty_array_put(self):
local_client = clone_client(Config.CLIENT)

key = local_client.key("EmptyArray", 1234)
local_client = datastore.Client()
entity = datastore.Entity(key=key)
entity["children"] = []
local_client.put(entity)
retrieved = local_client.get(entity.key)

self.assertEqual(entity["children"], retrieved["children"])
106 changes: 106 additions & 0 deletions tests/system/test_transaction.py
@@ -0,0 +1,106 @@
# Copyright 2011 Google LLC
#
# Licensed 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.

import pytest

from google.cloud import datastore
from google.cloud.exceptions import Conflict

from . import _helpers


def test_transaction_via_with_statement(datastore_client, entities_to_delete):
key = datastore_client.key("Company", "Google")
entity = datastore.Entity(key=key)
entity["url"] = u"www.google.com"

with datastore_client.transaction() as xact:
result = datastore_client.get(entity.key)
if result is None:
xact.put(entity)
entities_to_delete.append(entity)

# This will always return after the transaction.
retrieved_entity = datastore_client.get(key)

entities_to_delete.append(retrieved_entity)
assert retrieved_entity == entity


def test_transaction_via_explicit_begin_get_commit(
datastore_client, entities_to_delete,
):
# See
# github.com/GoogleCloudPlatform/google-cloud-python/issues/1859
# Note that this example lacks the threading which provokes the race
# condition in that issue: we are basically just exercising the
# "explict" path for using transactions.
before_1 = 100
before_2 = 0
transfer_amount = 40

key1 = datastore_client.key("account", "123")
account1 = datastore.Entity(key=key1)
account1["balance"] = before_1

key2 = datastore_client.key("account", "234")
account2 = datastore.Entity(key=key2)
account2["balance"] = before_2

datastore_client.put_multi([account1, account2])
entities_to_delete.append(account1)
entities_to_delete.append(account2)

xact = datastore_client.transaction()
xact.begin()
from_account = datastore_client.get(key1, transaction=xact)
to_account = datastore_client.get(key2, transaction=xact)
from_account["balance"] -= transfer_amount
to_account["balance"] += transfer_amount

xact.put(from_account)
xact.put(to_account)
xact.commit()

after1 = datastore_client.get(key1)
after2 = datastore_client.get(key2)
assert after1["balance"] == before_1 - transfer_amount
assert after2["balance"] == before_2 + transfer_amount


def test_failure_with_contention(datastore_client, entities_to_delete):
contention_prop_name = "baz"
local_client = _helpers.clone_client(datastore_client)

# Insert an entity which will be retrieved in a transaction
# and updated outside it with a contentious value.
key = local_client.key("BreakTxn", 1234)
orig_entity = datastore.Entity(key=key)
orig_entity["foo"] = u"bar"
local_client.put(orig_entity)

entities_to_delete.append(orig_entity)

with pytest.raises(Conflict):
with local_client.transaction() as txn:
entity_in_txn = local_client.get(key)

# Update the original entity outside the transaction.
orig_entity[contention_prop_name] = u"outside"
datastore_client.put(orig_entity)

# Try to update the entity which we already updated outside the
# transaction.
entity_in_txn[contention_prop_name] = u"inside"
txn.put(entity_in_txn)

0 comments on commit ad2023e

Please sign in to comment.