diff --git a/openstack_qa_tools/collectors/_delta.py b/openstack_qa_tools/collectors/_delta.py new file mode 100644 index 0000000..e824d65 --- /dev/null +++ b/openstack_qa_tools/collectors/_delta.py @@ -0,0 +1,46 @@ +# 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 json + + +def delta(previous, current): + product = {} + seen = set() + + # Old keys + for k, v in previous.items(): + if k not in current: + continue + newv = current[k] + if type(v) is not type(newv): + raise ValueError( + 'Type of key %s changed from %s to %s' % (k, + type(v), + type(newv))) + if isinstance(v, int) or isinstance(v, float): + product[k] = newv - v + elif isinstance(v, dict): + product[k] = delta(v, newv) + else: + raise ValueError('Only mappings of numbers are understood') + seen.add(k) + # New keys + for k in set(current.keys()) - seen: + product[k] = current[k] + return product + + +def delta_with_file(previous_path, current_data): + with open(previous_path) as previous_file: + previous_data = json.loads(previous_file.read()) + return delta(previous_data, current_data) diff --git a/openstack_qa_tools/tests/test_collectors_delta.py b/openstack_qa_tools/tests/test_collectors_delta.py new file mode 100644 index 0000000..fa4dc9e --- /dev/null +++ b/openstack_qa_tools/tests/test_collectors_delta.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# 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. + +""" +test_delta +---------------------------------- + +Tests for `openstack_qa_tools.collectors._delta` +""" + +import testscenarios + +from openstack_qa_tools.collectors import _delta +from openstack_qa_tools.tests import base + + +class TestOSQATDelta(testscenarios.WithScenarios, base.TestCase): + + scenarios = [ + ('add1', dict( + previous={'zoo': {'aardvark': 9, 'zebra': 0}}, + current={'zoo': {'aardvark': 12, 'zebra': 0}}, + expected={'zoo': {'aardvark': 3, 'zebra': 0}})), + ('newkey', dict( + previous={'zoo': {'bee': 0}}, + current={'lake': {'trout': 1}, 'zoo': {'bee': 5}}, + expected={'lake': {'trout': 1}, 'zoo': {'bee': 5}})), + ('delkey', dict( + previous={'zoo': {'cat': 99}}, + current={}, + expected={})), + ('newvar', dict( + previous={'zoo': {'dog': 9}}, + current={'zoo': {'dog': 9, 'ocelot': 2}}, + expected={'zoo': {'dog': 0, 'ocelot': 2}})), + ('delvar', dict( + previous={'zoo': {'elephant': 1000, 'bear': 1}}, + current={'zoo': {'elephant': 1000}}, + expected={'zoo': {'elephant': 0}})), + ('stringval', dict( + previous={'zoo': 'foo'}, + current={'zoo': 0}, + expected=ValueError)), + ('newstrval', dict( + previous={'zoo': {'giraffe': 100}}, + current={'zoo': {'giraffe': 'tall'}}, + expected=ValueError)), + ('changetype', dict( + previous={'zoo': {'horse': 7}}, + current={'zoo': 15}, + expected=ValueError)), + ] + + def test_delta(self): + if self.expected is ValueError: + self.assertRaises(ValueError, _delta.delta, self.previous, + self.current) + else: + self.assertEqual(self.expected, _delta.delta(self.previous, + self.current))