-
Notifications
You must be signed in to change notification settings - Fork 14.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: a simple LRUCache in frontend #20842
Conversation
Codecov Report
@@ Coverage Diff @@
## master #20842 +/- ##
==========================================
- Coverage 66.33% 66.27% -0.06%
==========================================
Files 1756 1758 +2
Lines 66769 66957 +188
Branches 7059 7106 +47
==========================================
+ Hits 44288 44375 +87
- Misses 20684 20767 +83
- Partials 1797 1815 +18
Flags with carried forward coverage won't be shown. Click here to find out more.
Help us with your feedback. Take ten seconds to tell us how you rate us. |
superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts
Outdated
Show resolved
Hide resolved
expect(() => lruCache(0)).toThrow(Error); | ||
}); | ||
|
||
test('LRU', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we break this into multiple tests? We could have one to test eviction, one to test invalid key types, and one to test cache clearing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review. This test case is for entire LRU operations. the L43 depends on the cache instance. The purpose of this test case is to prevent incorrect keys from being inserted at runtime, rather than compile time. the clear()
method also depends on the cache instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an example:
const newCache = () => {
const cache = lruCache<string>(3);
cache.set('1', 'a');
cache.set('2', 'b');
cache.set('3', 'c');
return cache;
};
test('initial LRU', () => {
expect(lruCache().capacity).toBe(100);
expect(lruCache(10).capacity).toBe(10);
expect(lruCache(10).size).toBe(0);
expect(() => lruCache(0)).toThrow(Error);
});
test('correctly evicts LRU value', () => {
const cache = newCache();
expect(cache.size).toBe(3);
cache.set('4', 'd');
expect(cache.has('1')).toBeFalsy();
expect(cache.get('1')).toBeUndefined();
});
test('correctly updates LRU value', () => {
const cache = newCache();
cache.get('1');
cache.set('5', 'e');
expect(cache.has('1')).toBeTruthy();
expect(cache.has('2')).toBeFalsy();
});
test('throws exception when key is invalid', () => {
const cache = lruCache<string>(3);
// @ts-ignore
expect(() => cache.set(0)).toThrow(TypeError);
});
test('clears the cache', () => {
const cache = newCache();
cache.clear();
expect(cache.size).toBe(0);
expect(cache.capacity).toBe(3);
});
This way, when a test fails, it's easier to identify what parts of the code are broken. It also avoids state dependencies between different test cases. You can reuse an initial state or optimize for each test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a test case, do we really need "modularity"? the original test case has 19 lines and the suggestion from you has 32 lines(removed empty line between test case).
the newCache
is also not a setup fixture, we can't use it in initial LRU
and LRU handle null and undefined
. This is a completely additional abstraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think readability and separation of concerns are more important than the number of lines. It's not a single test case because they are different requirements. You could have a bug related to value eviction but have the requirements for clearing the cache and type checking the keys still working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a modularity test case and all-in-one test case are not equivalent, because the each get
or set
method will affect the entire LRU.
For example:
test('LRU operation', () => {
const cache = lruCache<string>(3);
cache.set('1', 'a');
cache.set('2', 'b');
cache.set('3', 'c');
cache.set('4', 'd');
expect(cache.size).toBe(3);
....
not equal to
const newCache = () => {
const cache = lruCache<string>(3);
cache.set('1', 'a');
cache.set('2', 'b');
cache.set('3', 'c');
return cache;
};
test('correctly evicts LRU value', () => {
const cache = newCache();
expect(cache.size).toBe(3);
cache.set('4', 'd');
....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you move the expect after the set then they will be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code that I sent you is just an example. Feel free to improve it. For me, the important part is that the each requirement is in its own test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michael-s-molina Sorry, my point was that each call to get
and set
has an effect on the entire LRU, for example, the correctly evicts LRU value
and the correctly updates LRU value
should operate on a same LRU instance rather than separately. For this argument, we should probably get someone else to take a look at it as well, and I'd be happy to revise it, thank you very much for the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no problem in merging these two tests together since they are highly related 😉
superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the simplicity of this cache class, I think it's okay to structure basic operations in one test case. However, can we add a test for setting non-string keys? Cache should be expected to return value when trying to retrieve a value for non-string keys, too. (Or throw an error if keys are required to be strings.)
We can always add more tests once there is need to expand the functionalities. Or better yet, just use an established open source solutions (e.g. node-lru-cache) if more requirements arise.
superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts
Outdated
Show resolved
Hide resolved
@michael-s-molina @ktmud @stephenLYZ Thanks for the review, I will put your consideration into future iterations, if more requirements arise, use the open source solution. |
SUMMARY
the Drill to detail modal needs a LRUCache to handle response data in the client, so making a simple LRUCache in superset-ui/core.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A
TESTING INSTRUCTIONS
passes all CI
ADDITIONAL INFORMATION