# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
# pylint: disable=too-many-public-methods
"""Unit tests for the BackendNode and BackendNodeCollection classes."""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from collections import OrderedDict
from datetime import datetime
from uuid import UUID
from aiida.backends.testbase import AiidaTestCase
from aiida.common import timezone
from aiida.common import exceptions
[docs]class TestBackendNode(AiidaTestCase):
"""Test BackendNode."""
[docs] @classmethod
def setUpClass(cls, *args, **kwargs):
super(TestBackendNode, cls).setUpClass(*args, **kwargs)
cls.computer = cls.computer.backend_entity # Unwrap the `Computer` instance to `BackendComputer`
cls.user = cls.backend.users.create(email='tester@localhost').store()
[docs] def setUp(self):
super(TestBackendNode, self).setUp()
self.node_type = ''
self.node_label = 'label'
self.node_description = 'description'
self.node = self.backend.nodes.create(
node_type=self.node_type,
user=self.user,
computer=self.computer,
label=self.node_label,
description=self.node_description
)
[docs] def create_node(self):
return self.backend.nodes.create(node_type=self.node_type, user=self.user)
[docs] def test_creation(self):
"""Test creation of a BackendNode and all its properties."""
node = self.backend.nodes.create(
node_type=self.node_type, user=self.user, label=self.node_label, description=self.node_description
)
# Before storing
self.assertIsNone(node.id)
self.assertIsNone(node.pk)
self.assertTrue(isinstance(node.uuid, str))
self.assertTrue(isinstance(node.ctime, datetime))
self.assertIsNone(node.mtime)
self.assertIsNone(node.process_type)
self.assertEqual(node.attributes, dict())
self.assertEqual(node.extras, dict())
self.assertEqual(node.node_type, self.node_type)
self.assertEqual(node.label, self.node_label)
self.assertEqual(node.description, self.node_description)
# Store the node.ctime before the store as a reference
now = timezone.now()
node_ctime_before_store = node.ctime
self.assertTrue(now > node.ctime, '{} is not smaller than now {}'.format(node.ctime, now))
node.store()
node_ctime = node.ctime
node_mtime = node.mtime
# The node.ctime should have been unchanged, but the node.mtime should have changed
self.assertEqual(node.ctime, node_ctime_before_store)
self.assertIsNotNone(node.mtime)
self.assertTrue(now < node.mtime, '{} is not larger than now {}'.format(node.mtime, now))
# After storing
self.assertTrue(isinstance(node.id, int))
self.assertTrue(isinstance(node.pk, int))
self.assertTrue(isinstance(node.uuid, str))
self.assertTrue(isinstance(node.ctime, datetime))
self.assertTrue(isinstance(node.mtime, datetime))
self.assertIsNone(node.process_type)
self.assertEqual(node.attributes, dict())
self.assertEqual(node.extras, dict())
self.assertEqual(node.node_type, self.node_type)
self.assertEqual(node.label, self.node_label)
self.assertEqual(node.description, self.node_description)
# Try to construct a UUID from the UUID value to prove that it has a valid UUID
UUID(node.uuid)
# Change a column, which should trigger the save, update the mtime but leave the ctime untouched
node.label = 'test'
self.assertEqual(node.ctime, node_ctime)
self.assertTrue(node.mtime > node_mtime)
[docs] def test_creation_with_time(self):
"""
Test creation of a BackendNode when passing the mtime and the ctime. The passed ctime and mtime
should be respected since it is important for the correct import of nodes at the AiiDA import/export.
"""
from aiida.tools.importexport.dbimport.backends.utils import deserialize_attributes
ctime = deserialize_attributes('2019-02-27T16:20:12.245738', 'date')
mtime = deserialize_attributes('2019-02-27T16:27:14.798838', 'date')
node = self.backend.nodes.create(
node_type=self.node_type,
user=self.user,
label=self.node_label,
description=self.node_description,
mtime=mtime,
ctime=ctime
)
# Check that the ctime and mtime are the given ones
self.assertEqual(node.ctime, ctime)
self.assertEqual(node.mtime, mtime)
node.store()
# Check that the given values remain even after storing
self.assertEqual(node.ctime, ctime)
self.assertEqual(node.mtime, mtime)
[docs] def test_mtime(self):
"""Test the `mtime` is automatically updated when a database field is updated."""
node = self.node.store()
node_mtime = node.mtime
node.label = 'changed label'
self.assertTrue(node.mtime > node_mtime)
[docs] def test_clone(self):
"""Test the `clone` method."""
node = self.node.store()
clone = node.clone()
# Check that the clone is unstored, i.e. has *no* id, has a different UUID, but all other props are the same
self.assertIsNone(clone.id)
self.assertNotEqual(clone.uuid, node.uuid)
self.assertEqual(clone.label, node.label)
self.assertEqual(clone.description, node.description)
self.assertEqual(clone.user.id, node.user.id)
self.assertEqual(clone.computer.id, node.computer.id)
self.assertEqual(clone.attributes, node.attributes)
self.assertEqual(clone.extras, node.extras)
[docs] def test_property_setters(self):
"""Test the property setters of a BackendNode."""
label = 'new label'
description = 'new description'
self.node.label = label
self.node.description = description
self.assertEqual(self.node.label, label)
self.assertEqual(self.node.description, description)
[docs] def test_computer_methods(self):
"""Test the computer methods of a BackendNode."""
new_computer = self.backend.computers.create(name='localhost2', hostname='localhost').store()
self.assertEqual(self.node.computer.id, self.computer.id)
self.node.computer = new_computer
self.assertEqual(self.node.computer.id, new_computer.id)
[docs] def test_user_methods(self):
"""Test the user methods of a BackendNode."""
new_user = self.backend.users.create(email='newuser@localhost').store()
self.assertEqual(self.node.user.id, self.user.id)
self.node.user = new_user
self.assertEqual(self.node.user.id, new_user.id)
[docs] def test_get_set_attribute(self):
"""Test the `get_attribute` and `set_attribute` method of a BackendNode."""
attribute_1_name = 'a'
attribute_2_name = 'b'
attribute_3_name = 'c'
attribute_1_value = '1'
attribute_2_value = '2'
attribute_3_value = '3'
with self.assertRaises(AttributeError):
self.node.get_attribute(attribute_1_name)
self.assertFalse(self.node.is_stored)
self.node.set_attribute(attribute_1_name, attribute_1_value)
# Check that the attribute is set, but the node is not stored
self.assertFalse(self.node.is_stored)
self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value)
self.node.store()
# Check that the attribute is set, and the node is stored
self.assertTrue(self.node.is_stored)
self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value)
self.node.set_attribute(attribute_2_name, attribute_2_value)
self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value)
self.assertEqual(self.node.get_attribute(attribute_2_name), attribute_2_value)
reloaded = self.backend.nodes.get(self.node.pk)
self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value)
self.assertEqual(self.node.get_attribute(attribute_2_name), attribute_2_value)
reloaded.set_attribute(attribute_3_name, attribute_3_value)
self.assertEqual(reloaded.get_attribute(attribute_1_name), attribute_1_value)
self.assertEqual(reloaded.get_attribute(attribute_2_name), attribute_2_value)
self.assertEqual(reloaded.get_attribute(attribute_3_name), attribute_3_value)
# Check deletion of a single
reloaded.delete_attribute(attribute_1_name)
with self.assertRaises(AttributeError):
reloaded.get_attribute(attribute_1_name)
self.assertEqual(reloaded.get_attribute(attribute_2_name), attribute_2_value)
self.assertEqual(reloaded.get_attribute(attribute_3_name), attribute_3_value)
with self.assertRaises(AttributeError):
self.node.get_attribute(attribute_1_name)
[docs] def test_attributes(self):
"""Test the `BackendNode.attributes` property."""
node = self.create_node()
self.assertEqual(node.attributes, {})
node.set_attribute('attribute', 'value')
self.assertEqual(node.attributes, {'attribute': 'value'})
node.store()
self.assertEqual(node.attributes, {'attribute': 'value'})
[docs] def test_get_attribute(self):
"""Test the `BackendNode.get_attribute` method."""
node = self.create_node()
with self.assertRaises(AttributeError):
node.get_attribute('attribute')
node.set_attribute('attribute', 'value')
self.assertEqual(node.get_attribute('attribute'), 'value')
node.store()
self.assertEqual(node.get_attribute('attribute'), 'value')
[docs] def test_get_attribute_many(self):
"""Test the `BackendNode.get_attribute_many` method."""
node = self.create_node()
with self.assertRaises(AttributeError):
node.get_attribute_many(['attribute'])
node.set_attribute_many({'attribute': 'value', 'another': 'case'})
with self.assertRaises(AttributeError):
node.get_attribute_many(['attribute', 'unexisting'])
self.assertEqual(node.get_attribute_many(['attribute', 'another']), ['value', 'case'])
node.store()
self.assertEqual(node.get_attribute_many(['attribute', 'another']), ['value', 'case'])
[docs] def test_set_attribute(self):
"""Test the `BackendNode.set_attribute` method."""
node = self.create_node()
# When not stored, `set_attribute` will not clean values, so the following should be allowed
node.set_attribute('attribute_invalid', object())
node.set_attribute('attribute_valid', 'value')
# Calling store should cause the values to be cleaned which should raise
with self.assertRaises(exceptions.ValidationError):
node.store()
# Replace the original invalid with a valid value
node.set_attribute('attribute_invalid', 'actually valid')
node.store()
self.assertEqual(node.get_attribute_many(['attribute_invalid', 'attribute_valid']), ['actually valid', 'value'])
# Raises immediately when setting it if already stored
with self.assertRaises(exceptions.ValidationError):
node.set_attribute('attribute', object())
[docs] def test_set_attribute_many(self):
"""Test the `BackendNode.set_attribute_many` method."""
# Calling `set_attribute_many` on an unstored node
node = self.create_node()
# When not stored, `set_attribute` will not clean values, so the following should be allowed
node.set_attribute_many({'attribute_invalid': object(), 'attribute_valid': 'value'})
# Calling store should cause the values to be cleaned which should raise
with self.assertRaises(exceptions.ValidationError):
node.store()
# Replace the original invalid with a valid value
node.set_attribute_many({'attribute_invalid': 'actually valid'})
node.store()
self.assertEqual(node.get_attribute_many(['attribute_invalid', 'attribute_valid']), ['actually valid', 'value'])
attributes = OrderedDict()
attributes['another_attribute'] = 'value'
attributes['attribute_invalid'] = object()
# Raises immediately when setting it if already stored
with self.assertRaises(exceptions.ValidationError):
node.set_attribute_many(attributes)
self.assertTrue('another_attribute' not in node.attributes)
attributes = {'attribute_one': 1, 'attribute_two': 2}
# Calling `set_attribute_many` on a stored node
node = self.create_node()
node.store()
node.set_attribute_many(attributes)
self.assertEqual(node.attributes, attributes)
[docs] def test_reset_attributes(self):
"""Test the `BackendNode.reset_attributes` method."""
node = self.create_node()
attributes_before = {'attribute_one': 1, 'attribute_two': 2}
attributes_after = {'attribute_three': 3, 'attribute_four': 4}
# Reset attributes on an unstored node
node.set_attribute_many(attributes_before)
self.assertEqual(node.attributes, attributes_before)
node.reset_attributes(attributes_after)
self.assertEqual(node.attributes, attributes_after)
# Reset attributes on stored node
node = self.create_node()
node.store()
node.set_attribute_many(attributes_before)
self.assertEqual(node.attributes, attributes_before)
node.reset_attributes(attributes_after)
self.assertEqual(node.attributes, attributes_after)
[docs] def test_delete_attribute(self):
"""Test the `BackendNode.delete_attribute` method."""
node = self.create_node()
with self.assertRaises(AttributeError):
node.delete_attribute('notexisting')
node.set_attribute('attribute', 'value')
node.delete_attribute('attribute')
self.assertEqual(node.attributes, {})
# Now for a stored node
node = self.create_node().store()
with self.assertRaises(AttributeError):
node.delete_attribute('notexisting')
node.set_attribute('attribute', 'value')
node.delete_attribute('attribute')
self.assertEqual(node.attributes, {})
[docs] def test_delete_attribute_many(self):
"""Test the `BackendNode.delete_attribute_many` method."""
node = self.create_node()
attributes = {'attribute_one': 1, 'attribute_two': 2}
with self.assertRaises(AttributeError):
node.delete_attribute_many(['notexisting', 'some'])
node.set_attribute_many(attributes)
with self.assertRaises(AttributeError):
node.delete_attribute_many(['attribute_one', 'notexisting'])
# Because one key failed during delete, none of the attributes should have been deleted
self.assertTrue('attribute_one' in node.attributes)
# Now delete the keys that actually should exist
node.delete_attribute_many(attributes.keys())
self.assertEqual(node.attributes, {})
# Now for a stored node
node = self.create_node().store()
with self.assertRaises(AttributeError):
node.delete_attribute_many(['notexisting', 'some'])
node.set_attribute_many(attributes)
with self.assertRaises(AttributeError):
node.delete_attribute_many(['attribute_one', 'notexisting'])
# Because one key failed during delete, none of the attributes should have been deleted
self.assertTrue('attribute_one' in node.attributes)
# Now delete the keys that actually should exist
node.delete_attribute_many(attributes.keys())
self.assertEqual(node.attributes, {})
[docs] def test_clear_attributes(self):
"""Test the `BackendNode.clear_attributes` method."""
node = self.create_node()
attributes = {'attribute_one': 1, 'attribute_two': 2}
node.set_attribute_many(attributes)
self.assertEqual(node.attributes, attributes)
node.clear_attributes()
self.assertEqual(node.attributes, {})
# Now for a stored node
node = self.create_node().store()
node.set_attribute_many(attributes)
self.assertEqual(node.attributes, attributes)
node.clear_attributes()
self.assertEqual(node.attributes, {})
[docs] def test_attribute_items(self):
"""Test the `BackendNode.attribute_items` generator."""
node = self.create_node()
attributes = {'attribute_one': 1, 'attribute_two': 2}
node.set_attribute_many(attributes)
self.assertEqual(attributes, dict(node.attributes_items()))
# Repeat for a stored node
node = self.create_node().store()
attributes = {'attribute_one': 1, 'attribute_two': 2}
node.set_attribute_many(attributes)
self.assertEqual(attributes, dict(node.attributes_items()))
[docs] def test_attribute_keys(self):
"""Test the `BackendNode.attribute_keys` generator."""
node = self.create_node()
attributes = {'attribute_one': 1, 'attribute_two': 2}
node.set_attribute_many(attributes)
self.assertEqual(set(attributes), set(node.attributes_keys()))
# Repeat for a stored node
node = self.create_node().store()
attributes = {'attribute_one': 1, 'attribute_two': 2}
node.set_attribute_many(attributes)
self.assertEqual(set(attributes), set(node.attributes_keys()))
[docs] def test_attribute_flush_specifically(self):
"""Test that changing `attributes` only flushes that property and does not affect others like extras.
Regression fix for #3338
"""
node = self.create_node().store()
extras = {'extra_one': 1, 'extra_two': 2}
node.set_extra_many(extras)
node.store()
# Load the stored node in memory in another instance and add another extra
reloaded = self.backend.nodes.get(node.pk)
reloaded.set_extra('extra_three', 3)
# The original memory instance should now not include `extra_three`. Changing an attribute should only flush
# the attributes and not the whole node state, which would override for example also the extras.
node.set_attribute('attribute_one', 1)
# Reload the node yet again and verify that the `extra_three` extra is still there
rereloaded = self.backend.nodes.get(node.pk)
self.assertIn('extra_three', rereloaded.extras.keys())