-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Smoke unit test for redis migrations #9117
base: develop
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## develop #9117 +/- ##
===========================================
+ Coverage 73.24% 73.31% +0.06%
===========================================
Files 447 448 +1
Lines 45837 45843 +6
Branches 3908 3908
===========================================
+ Hits 33573 33609 +36
+ Misses 12264 12234 -30
|
self.addCleanup(self.test_migration.cleanup) | ||
|
||
call_command("migrateredis") | ||
exp_migrations = { |
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.
- Could you please explain the purpose of generation test migration? why existing migrations are not enough? I do not really think that we need to modify/add files from
cvat/apps
when running tests. Otherwise, the generated files may not be removed e.g. when we stop debugging beforecleanup
is called. - Do I understand correctly that we need to update this test each time when a new Redis migration is added?
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.
All the existing migration files are correct, which means I cannot test a scenario where the tool throws LoaderError
.
Though, case 1 could be rewritten to use existing migration files
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.
we need to update this test each time when a new Redis migration is added?
I didn't get it, why would that be the case? Tests are using fake Redis database
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 didn't get it, why would that be the case? Tests are using fake Redis database
I'm talking about the case when we add one more redis migration. In that case the self.assertEqual(conn.smembers("cvat:applied_migrations"), exp_migrations)
will fail. Please, check that the expected by the test migration keys are added to cvat:applied_migrations
instead of comparing all set members.
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.
Yes totally makes sense, applied
self.assertEqual(conn.smembers("cvat:applied_migrations"), exp_migrations) | ||
self.test_migration.runner_mock.assert_called_once() | ||
|
||
def test_migration_not_applied(self, redis1, _): |
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 guess the more appropriate test should cover a case when migrateredis
is called with --check
arg.
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.
Makes sense
BAD_MIGRATION_FILE_CONTENT = MIGRATION_FILE_CONTENT.replace("(BaseMigration)", "") | ||
|
||
with ( | ||
patch(f"{__name__}.MIGRATION_FILE_CONTENT", new=BAD_MIGRATION_FILE_CONTENT), |
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.
It looks a bit odd to me that you patch MIGRATION_FILE_CONTENT
defined in the current module and not used anywhere else.
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.
Makes sense, reworked the class to only generate bad migration
MUT = "cvat.apps.redis_handler" | ||
|
||
|
||
@patch(f"{MUT}.migration_loader.Redis", return_value=fakeredis.FakeRedis()) |
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.
Why do we need to patch it? It is used only as type annotations in the migration_loader
module.
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.
Yes you're right, removing this
|
||
from .utils import path_to_module | ||
|
||
WORKDIR = PosixPath("cvat/apps") |
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.
Path
? Tests can be run on windows
from pathlib import PosixPath | ||
|
||
|
||
def path_to_module(path: PosixPath) -> str: |
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.
Path
?
self.patcher: _patch = patch.object(self.test_class, "run") | ||
self.runner_mock: Mock | None = self.patcher.start() | ||
|
||
self.exp_migration_name = app_name + "." + self.make_migration_name() |
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.
So, why did you decide not to check whether engine.001_cleanup_scheduled_jobs
is added to cvat:applied_migrations
after running migrateredis
?
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 misunderstood, I thought --check
would be enough for this. Applying
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.
So I reworked it to this: now the it looks for all cvat/apps/**/redis_migrations/[0-9]*.py
files, extracts the expected migration names and asserts they are identical to members of cvat:applied_migrations
self.patcher = patch.object(self.test_class, "run") | ||
self.runner_mock: Mock | None = self.patcher.start() |
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.
Why do we need to initialize patcher
and runner_mock
here? Would it be better just to mock the method in the corresponding test?
with patch.object(self.test_migration.test_class, "run") as mock_run:
mock_run.side_effect = original_run_method
...
mock_run.assert_not_called()
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.
Good point, less hassle with the cleanup
|
||
def test_migration_bad(self, redis): | ||
with redis() as conn: | ||
conn.flushall() |
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 don't think that this test should flush Redis data
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.
Removing
file_to_migration_name(file).encode("utf8") for file in migration_files | ||
) | ||
with redis() as conn: | ||
applied_migrations = conn.smembers("cvat:applied_migrations") |
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.
applied_migrations = conn.smembers("cvat:applied_migrations") | |
applied_migrations = conn.smembers(AppliedMigration.SET_KEY) |
migration_files = WORKDIR.glob(MIGRATION_FILES) | ||
expected_migrations = set( | ||
file_to_migration_name(file).encode("utf8") for file in migration_files | ||
) |
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.
It's better to use existing logic instead of adding custom file_to_migration_name
migration_files = WORKDIR.glob(MIGRATION_FILES) | |
expected_migrations = set( | |
file_to_migration_name(file).encode("utf8") for file in migration_files | |
) | |
expected_migrations = { | |
f"{app_config.label}.{f.stem}".encode() | |
for app_config in MigrationLoader.find_app_configs() | |
for f in (Path(app_config.path) / MigrationLoader.REDIS_MIGRATIONS_DIR_NAME).glob("[0-9]*.py") | |
} | |
assert len(expected_migrations) |
|
Motivation and context
Basic coverage for redis migrations support from #8898
How has this been tested?
Prepare
.py
migration file with an incorrect migration class is generatedfakeredis
instance throughout the whole test suite since our main concern is the migration tool itself, not the Redis database.Case 1: Migration is added and applied successfully
migrateredis --check
should exit the programmigrateredis
, should return correctlycvat:applied_migrations
keyCase 2: Migration is not added and not applied
000_second.py
with the following contents:Since the Migration class is not a subclass of BaseMigration, we expect that
LoaderError
is raisedcvat:applied_migrations
is an empty setMigration.run
is not calledChecklist
develop
branchLicense
Feel free to contact the maintainers if that's a concern.