Skip to content

Commit

Permalink
Merge pull request #440 from sebhub/improve-ext-attr-fingerprint
Browse files Browse the repository at this point in the history
Improve fingerprinting of extended attributes
  • Loading branch information
jacebrowning committed Nov 30, 2019
2 parents 044f26c + e76b6bc commit 2451a07
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 31 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,10 @@
# 2.1 (future)

- **BREAKING:** The fingerprint calculation method of reviewed extended
attributes changed. Use `doorstop review all` followed by `doorstop clear all`
to update an existing project. WARNING: This marks all items as reviewed and
clears all suspect links.

# 2.0 (2019-11-29)

- Dropped support for Python 3.5.
Expand Down
16 changes: 10 additions & 6 deletions docs/reference/item.md
Expand Up @@ -381,9 +381,13 @@ attributes:
```

If attributes listed in `reviewed` do not exist in an item of this document,
then a warning is issued by the validation command `doorstop`:

```
WARNING: REQ001: missing extended reviewed attribute: type
WARNING: REQ001: missing extended reviewed attribute: verification-method
```
then these attributes are skipped in the fingerprint calculation and no warning
is issued by the validation command `doorstop`. Validation of items against a
template should be done by third-party tools. Changing the order of reviewed
attributes listed in the document configuration changes the fingerprint of
existing item of the documents which have these attributes. Adding new
reviewed attributes to the document configuration does not change the
fingerprint of existing items of the document, if they do not have them,
otherwise the fingerprint changes. Removing a reviewed attribute from the
document configuration changes the fingerprint of all items of the document
with such an attribute.
30 changes: 24 additions & 6 deletions doorstop/core/item.py
Expand Up @@ -52,6 +52,29 @@ def _convert_to_yaml(indent, prefix, value):
return value


def _convert_to_str(value, result):
"""Convert value to a string serialization.
This function is independent of the YAML format and may be used for data
which should be independent of the actual item storage format. It depends
only on the Python sorting function and string representation.
:param value: the value to convert
:param result: the current result of the string serialization
:return: the updated result of the string serialization
"""
if isinstance(value, list):
for v in value:
result = _convert_to_str(v, result)
return result
if isinstance(value, dict):
for k in sorted(value.keys()):
result = _convert_to_str(value[k], result)
return result
return result + str(value)


def requires_tree(func):
"""Require a tree reference."""

Expand Down Expand Up @@ -738,12 +761,7 @@ def stamp(self, links=False):
if links:
values.extend(self.links)
for key in self.document.extended_reviewed:
if key in self._data:
values.append(self._dump(self._data[key]))
else:
log.warning(
"{}: missing extended reviewed attribute: {}".format(self.uid, key)
)
values.append(_convert_to_str(self._data.get(key, ""), ""))
return Stamp(*values)

@auto_save
Expand Down
59 changes: 40 additions & 19 deletions doorstop/core/tests/test_item.py
Expand Up @@ -9,7 +9,7 @@
from typing import List
from unittest.mock import MagicMock, Mock, patch

from doorstop import common, core
from doorstop import common
from doorstop.common import DoorstopError
from doorstop.core.item import Item, UnknownItem
from doorstop.core.tests import (
Expand Down Expand Up @@ -799,40 +799,61 @@ def test_stamp_with_one_extended_reviewed(self):
"""Verify fingerprint with one extended reviewed attribute."""
self.item._data['type'] = 'functional'
self.item.document.extended_reviewed = ['type']
stamp = '5ijLUBTXGCkN-2wctQTQ5cl2-ZTDMeukDlXDy0OBCGg='
stamp = 'HViLqscmSeVfv2jYBFhXdceTcEWWc0r9uchEmX7xSTY='
self.assertEqual(stamp, self.item.stamp())
self.item.document.extended_reviewed = []
stamp = 'OoHOpBnrt8us7ph8DVnz5KrQs6UBqj_8MEACA0gWpjY='
self.assertEqual(stamp, self.item.stamp())

def test_stamp_with_complex_extended_reviewed(self):
"""Verify fingerprint with complex extended reviewed attribute."""
self.item._data['attr'] = ['a', 'b', ['c', {'d': 'e', 'f': ['g']}]]
self.item.document.extended_reviewed = ['attr']
stamp = 'H1frEDRLk8y7eaNQPpGbgpKlWLqXc3_QfiCq1qvrUtA='
self.assertEqual(stamp, self.item.stamp())

def test_stamp_with_two_extended_reviewed(self):
"""Verify fingerprint with two extended reviewed attributes."""
self.item._data['type'] = 'functional'
self.item._data['verification-method'] = 'test'
self.item.document.extended_reviewed = ['type', 'verification-method']
stamp = 'xmXpN4L0mNHm8-5Ga24VJLc5b9J2ttG4G8XVGrgDFeU='
stamp = 'S_yJkuwMTVG70Pcr3R6zdSR1VdviwxVWgG7q5b5NpjU='
self.assertEqual(stamp, self.item.stamp())

def test_stamp_with_reversed_extended_reviewed_reverse(self):
def test_stamp_with_reversed_extended_reviewed(self):
"""Verify fingerprint with reversed extended reviewed attributes."""
self.item._data['type'] = 'functional'
self.item._data['verification-method'] = 'test'
self.item.document.extended_reviewed = ['verification-method', 'type']
stamp = '2HCtrWC2tYEpFpCtNKf-D4n_s0IrxuEuiF-6cZ6wdr0='
stamp = 'OhVM3nMW4mensMfWA-VIcbn5XYbxpPI_ZEDVcCjoGo0='
self.assertEqual(stamp, self.item.stamp())

def test_stamp_with_missing_extended_reviewed_reverse(self):
"""Verify fingerprint with missing extended reviewed attribute."""
with ListLogHandler(core.item.log) as handler:
self.item._data['type'] = 'functional'
self.item._data['verification-method'] = 'test'
self.item.document.extended_reviewed = [
'missing',
'type',
'verification-method',
]
stamp = 'xmXpN4L0mNHm8-5Ga24VJLc5b9J2ttG4G8XVGrgDFeU='
self.assertEqual(stamp, self.item.stamp())
self.assertIn(
"RQ001: missing extended reviewed attribute: missing", handler.records
)
"""Verify fingerprint with missing extended reviewed attributes."""
self.item._data['type'] = 'functional'
self.item._data['verification-method'] = 'test'
self.item.document.extended_reviewed = [
'missing',
'type',
'verification-method',
]
stamp = 'S_yJkuwMTVG70Pcr3R6zdSR1VdviwxVWgG7q5b5NpjU='
self.assertEqual(stamp, self.item.stamp())
self.item.document.extended_reviewed = [
'missing',
'type',
'verification-method',
'missing-2',
]
stamp = 'S_yJkuwMTVG70Pcr3R6zdSR1VdviwxVWgG7q5b5NpjU='
self.assertEqual(stamp, self.item.stamp())
self.item.document.extended_reviewed = [
'type',
'verification-method',
'missing-2',
]
stamp = 'S_yJkuwMTVG70Pcr3R6zdSR1VdviwxVWgG7q5b5NpjU='
self.assertEqual(stamp, self.item.stamp())

def test_stamp_links(self):
"""Verify an item's contents can be stamped."""
Expand Down

0 comments on commit 2451a07

Please sign in to comment.