Skip to content

Conversation

@sobolevn
Copy link
Member

@sobolevn sobolevn commented Oct 31, 2021

I've added several extra tests to make sure case in 45679 works as intended.

https://bugs.python.org/issue45701

Context: https://bugs.python.org/issue45679

@sobolevn
Copy link
Member Author

sobolevn commented Nov 3, 2021

CC @Fidget-Spinner

@rhettinger
Copy link
Contributor

rhettinger commented Nov 4, 2021

I appreciate the effort to add tests but am dubious about these.

On a cold reading, I find it difficult to tell what guaranteed invariants are being checked or even what the test is doing. If one of these tests ever broke, it would take a while to figure out what the complaint was about.

Also, the assertions about the cache_info components seem to be over-specified. We make almost no promises about the cache hits and have more than once changed what counts as a hit.

My recommendation is to just add a couple of lines to test_lru_with_types():

# Verify distinct results for equivalent tuples with differing types
cached_repr = self.module.lru_cache(maxsize=maxsize, typed=True)(repr)
self.assertEqual(cached_repr((True, 'string')), "(True, 'string')")
self.assertEqual(cached_repr((1, 'string')), "(1, 'string')")

I don't think there is a need for a new test method that makes heroic efforts to test in combination that which has already been tested piece by piece. A single additional test will suffice to cover the notion, "that which works for scalars also works in containers".

@rhettinger rhettinger self-assigned this Nov 4, 2021
self.assertEqual(cached.cache_info().misses, 2)

def test_lru_with_container_types_hash_collision(self):
# https://bugs.python.org/issue45701
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would normally only reference a bug if it pertained to a bug in the lru_cache itself. In this case, the referenced bug was in application code. Linking to it from this from here doesn't add any explanatory value.

@sobolevn
Copy link
Member Author

sobolevn commented Nov 4, 2021

@rhettinger thanks a lot for the detailed feedback!

My recommendation is to just add a couple of lines to test_lru_with_types():

# Verify distinct results for equivalent tuples with differing types
cached_repr = self.module.lru_cache(maxsize=maxsize, typed=True)(repr)
self.assertEqual(cached_repr((True, 'string')), "(True, 'string')")
self.assertEqual(cached_repr((1, 'string')), "(1, 'string')")

The thing is, it does not not work this way. And that's what we test here 🙂

>>> from functools import lru_cache
>>> r = lru_cache(typed=True)(repr)
>>> r((1, 2))
'(1, 2)'
>>> r((True, 2))
'(1, 2)'
>>> r((1.0, 2))
'(1, 2)'

Also, the assertions about the cache_info components seem to be over-specified. We make almost no promises about the cache hits and have more than once changed what counts as a hit.

I took the idea from the similar tests in this module. There're around 50 usages of .cache_info() and most of them assume hits / misses:

» ag '.cache_info()' Lib/test/test_functools.py 
1217:        self.assertEqual(fib.cache_info(),
1220:        self.assertEqual(fib.cache_info(),
1230:        hits, misses, maxsize, currsize = f.cache_info()
1242:        hits, misses, maxsize, currsize = f.cache_info()
1248:        hits, misses, maxsize, currsize = f.cache_info()
1253:        hits, misses, maxsize, currsize = f.cache_info()
1261:        hits, misses, maxsize, currsize = f.cache_info()
1272:        self.assertEqual(f.cache_info().maxsize, 0)
1277:        hits, misses, maxsize, currsize = f.cache_info()
1288:        self.assertEqual(f.cache_info().maxsize, 1)
1293:        hits, misses, maxsize, currsize = f.cache_info()
1304:        self.assertEqual(f.cache_info().maxsize, 2)
1310:        hits, misses, maxsize, currsize = f.cache_info()
1322:        self.assertEqual(square.cache_info().hits, 1)
1323:        self.assertEqual(square.cache_info().misses, 2)
1324:        self.assertEqual(square.cache_info().maxsize, 128)
1325:        self.assertEqual(square.cache_info().currsize, 2)
1348:        self.assertEqual(f.cache_info().currsize, 10)
1352:        self.assertEqual(f.cache_info().currsize, 10)
1366:        self.assertEqual(f.cache_info().hits, 1)
1386:        self.assertEqual(f.cache_info(), (0, 1, 1, 1))
1391:        self.assertEqual(f.cache_info(), (1, 1, 1, 1))
1396:        self.assertEqual(f.cache_info(), (1, 2, 1, 1))
1401:        self.assertEqual(f.cache_info(), (1, 3, 1, 1))
1451:        self.assertEqual(fib.cache_info(),
1454:        self.assertEqual(fib.cache_info(),
1463:        self.assertEqual(eq.cache_info(),
1495:            self.assertEqual(square.cache_info().hits, 4)
1496:            self.assertEqual(square.cache_info().misses, 4)
1516:                    self.assertEqual(cached.cache_info().hits, 2)
1517:                    self.assertEqual(cached.cache_info().misses, 2)
1548:                    self.assertEqual(cached.cache_info().hits, 3)
1549:                    self.assertEqual(cached.cache_info().misses, 1)
1561:        self.assertEqual(fib.cache_info(),
1564:        self.assertEqual(fib.cache_info(),
1575:        self.assertEqual(fib.cache_info(),
1578:        self.assertEqual(fib.cache_info(),
1588:        self.assertEqual(f.cache_info(),
1604:        hits, misses, maxsize, currsize = f.cache_info()
1627:            hits, misses, maxsize, currsize = f.cache_info()
1657:        self.assertEqual(f.cache_info(), (0, 0, m*n, 0))
1672:                self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1))
1721:        self.assertEqual(X.f.cache_info(), (0, 0, 2, 0))
1726:        self.assertEqual(X.f.cache_info(), (4, 6, 2, 2))
1731:        self.assertEqual(X.f.cache_info(), (10, 10, 2, 2))
1736:        self.assertEqual(X.f.cache_info(), (15, 15, 2, 2))
1738:        self.assertEqual(a.f.cache_info(), X.f.cache_info())
1739:        self.assertEqual(b.f.cache_info(), X.f.cache_info())
1740:        self.assertEqual(c.f.cache_info(), X.f.cache_info())

I don't think there is a need for a new test method that makes heroic efforts to test in combination that which has already been tested piece by piece

I am not sure I follow 🙂
There's no test at the moment which test this corner case.

All this being said, I can easily change this to be a simple one / two lines test. But, I'll add some more explanation context to tests' docstrings first. Maybe it will be easier to read / maintain. And possibly we can keep more complex checks.

@sobolevn
Copy link
Member Author

sobolevn commented Nov 4, 2021

But, I'll add some more explanation context to tests' docstrings first.

Done! I've simplified tests as much as possible. Including:

  1. I've removed maxsize checks. They are not important in this context
  2. I've removed the first test, because it is not special. It just works
  3. I've changed the second test to use repr as @rhettinger suggested. Now it looks more readable: it clearly indicates what we expect
  4. I've also added some docstring explanation for the future readers

I hope it is good enough 🙂
If not, I would be happy to fix it!

@rhettinger
Copy link
Contributor

The thing is, it does not not work this way.

Yes, now I see. The tested invariant is that typed applies to the container rather than its contents.

Would it suffice to add a single test case, test_typed_is_not_recursive():

>>> @lru_cache(typed=True)
... def myrepr(obj):
...     return repr(obj)
...
>>> myrepr(1)
'1'
>>> myrepr(True)
'True'
>>> myrepr((True, 'x'))
"(True, 'x')"
>>> myrepr((1, 'x'))
"(True, 'x')"
>>> class T(tuple):
...     pass
... 
>>> myrepr(T((1, 'x')))
"(1, 'x')"

@rhettinger
Copy link
Contributor

Thank you for the PR.

@sobolevn
Copy link
Member Author

sobolevn commented Nov 5, 2021

Thanks a lot for the great simplification 👍

remykarem pushed a commit to remykarem/cpython that referenced this pull request Dec 7, 2021
remykarem pushed a commit to remykarem/cpython that referenced this pull request Jan 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Tests in the Lib/test dir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants