Repository: Nicoretti/crc

Issue:
🐞 Return of digest() is not stable
# Summary
Multiple calls to `digest()` don't return the same answers.

## Reproducing the Issue
```
>>> import crc
>>> r = crc.Register(crc.Crc8.BLUETOOTH)
>>> r.init()
>>> r.update(b"Hello World!")
138
>>> r.digest()
81
>>> r.digest()
138
>>> r.digest()
81
```

## Expected Behaviour
Expected multiple calls to `digest()` to return the same value.

## Actual Behaviour
Return value flip-flops between two values.

## Root Cause (optional)
I think it is caused by in-place reverse in `digest()`, [L256-L257](https://github.com/Nicoretti/crc/blob/8d928145df5caf588d9ff5af83bd72feb241fd1e/src/crc/_crc.py#L256-L257)



Discussion:

Patch:
diff --git a/docs/docs/changelog/unreleased.md b/docs/docs/changelog/unreleased.md
index 30c1938..49f4d41 100644
--- a/docs/docs/changelog/unreleased.md
+++ b/docs/docs/changelog/unreleased.md
@@ -1,5 +1,12 @@
 # Unreleased
 
+## 🐞 Bug Fixes
+* Fixed unstable return values of `digest` function. For more details, see issue [#151](https://github.com/Nicoretti/crc/issues/151).
+
+    !!! bug
+        This issue specifically affected scenarios where the CRC register was manually manipulated. Standard usages of the `Calculator` class were not impacted.
+        Furthermore, this issue primarily occurred in configurations that required reverse output.
+
 ## 📚 Documentation
 * Add overview of crc configurations
 
diff --git a/pyproject.toml b/pyproject.toml
index a38e36a..e3c2a8a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "crc"
-version = "6.1.1"
+version = "6.1.2"
 description = "Library and CLI to calculate and verify all kinds of CRC checksums"
 packages = [
     { include = "crc", from = "src" },
diff --git a/src/crc/_crc.py b/src/crc/_crc.py
index fb7a546..72c8779 100644
--- a/src/crc/_crc.py
+++ b/src/crc/_crc.py
@@ -253,9 +253,8 @@ class BasicRegister(AbstractRegister):
         """
         See `AbstractRegister.digest`
         """
-        if self._config.reverse_output:
-            self.register = self.reverse()
-        return self.register ^ self._config.final_xor_value
+        value = self.reverse() if self._config.reverse_output else self.register
+        return value ^ self._config.final_xor_value
 
     def reverse(self) -> int:
         """


Test patch:
diff --git a/test/unit/regression/test_unstable_digest.py b/test/unit/regression/test_unstable_digest.py
new file mode 100644
index 0000000..ea7f7f7
--- /dev/null
+++ b/test/unit/regression/test_unstable_digest.py
@@ -0,0 +1,42 @@
+"""
+This module contains regression tests regarding the issue of unstable return values from the `digest` function.
+The tests ensure that the `digest` function consistently returns the expected output for given inputs.
+For more context and a detailed discussion of the problem, refer to the GitHub issue:
+https://github.com/Nicoretti/crc/issues/151
+"""
+
+import itertools
+
+import pytest
+
+import crc
+
+
+def test_original_regression():
+    reg = crc.Register(crc.Crc8.BLUETOOTH)
+    reg.init()
+    reg.update(b"Hello World!")
+
+    times = 10
+    expected = [81 for _ in range(0, times)]
+    actual = [reg.digest() for _ in range(0, times)]
+
+    assert actual == expected
+
+
+@pytest.mark.parametrize(
+    "configuration,times,expected",
+    [
+        (config, 10, crc.Calculator(config).checksum(b"Hello World!"))
+        for config in itertools.chain(crc.Crc8, crc.Crc16, crc.Crc32, crc.Crc64)
+    ],
+)
+def test_digest_is_stable(configuration, times, expected):
+    expected = [expected for _ in range(times)]
+
+    reg = crc.Register(configuration)
+    reg.init()
+    reg.update(b"Hello World!")
+    actual = [reg.digest() for _ in range(times)]
+
+    assert actual == expected