Skip to content

Conversation

@artem-vavilov
Copy link
Member

@artem-vavilov artem-vavilov commented Jan 6, 2026

Wraps setup_all callbacks in a class-level DB transaction (rolled back in teardown_all) so records created in setup_all cannot leak across tests, without relying on use_transactional_test_case

Links

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the transactional test case implementation to ensure database changes are automatically rolled back after tests. The key change is moving from an opt-in use_transactional_test_case class attribute to leveraging Rails' built-in use_transactional_tests feature, which is now explicitly enabled by default in TransactionalTestCase.

Key Changes:

  • Simplified TransactionalTestCase module to use Rails' built-in transactional test support
  • Modified setup_all to wrap its block in a database transaction that's rolled back in teardown_all
  • Removed all manual self.use_transactional_test_case assignments across the codebase since transactional behavior is now enabled by default

Reviewed changes

Copilot reviewed 121 out of 121 changed files in this pull request and generated no comments.

Show a summary per file
File Description
dashboard/test/testing/transactional_test_case.rb Core refactoring: simplified to use Rails' use_transactional_tests and wrapped setup_all blocks in transactions
dashboard/test/testing/test_case_test.rb Added test coverage for the new transaction behavior in setup_all
dashboard/test/models/*.rb (45 files) Removed redundant use_transactional_test_case assignments
dashboard/test/lib/*.rb (21 files) Removed redundant use_transactional_test_case assignments
dashboard/test/controllers/*.rb (43 files) Removed redundant use_transactional_test_case assignments
dashboard/test/integration/*.rb (3 files) Removed redundant use_transactional_test_case assignments
dashboard/test/helpers/*.rb (2 files) Removed redundant use_transactional_test_case assignments
dashboard/test/models/pd/application/principal_approval_application_test.rb Changed setup_all to setup (no longer needs transaction wrapping)
dashboard/test/lib/services/complete_application_reminder_test.rb Changed setup_all to setup (no longer needs transaction wrapping)
dashboard/test/lib/pd/survey_pipeline/mapper_test.rb Changed setup_all to setup (data setup doesn't require persistence)
dashboard/test/lib/pd/survey_pipeline/daily_survey_joiner_test.rb Changed setup_all to setup (data setup doesn't require persistence)
dashboard/test/controllers/api/v1/pd/teacher_attendance_report_controller_test.rb Consolidated setup_all time travel into setup block

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


setup_all do
if use_transactional_test_case?
setup_fixtures
Copy link
Member Author

Choose a reason for hiding this comment

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

@davidsbailey
Copy link
Member

this could be worth trying, but I am concerned that use_transactional_tests might not be protecting us in the way that we want, because I was able to show here that without use_transactional_test_case, there would be collisions between objects across test cases: f7a0d43 . if use_transactional_tests does what it says, then why didn't it protect us in that case?

if you want to continue down this path, my recommendation would be to do a similar test to the commit linked above, as proof that we are cleaning up between tests. because we run tests 7 ways parallel in drone, I would recommend adding the colliding objects to at least 10 different setup_all blocks.

@artem-vavilov
Copy link
Member Author

this could be worth trying, but I am concerned that use_transactional_tests might not be protecting us in the way that we want, because I was able to show here that without use_transactional_test_case, there would be collisions between objects across test cases: f7a0d43 . if use_transactional_tests does what it says, then why didn't it protect us in that case?

if you want to continue down this path, my recommendation would be to do a similar test to the commit linked above, as proof that we are cleaning up between tests. because we run tests 7 ways parallel in drone, I would recommend adding the colliding objects to at least 10 different setup_all blocks.

use_transactional_tests opens a transaction for each test case (the test or it blocks, d.f test_..., etc.), while setup_all runs earlier at the class level and wraps all test cases in the class.

So use_transactional_tests basically covers that part:

def before_setup
begin_transaction
super
end
def after_teardown
super
rollback_transaction
end

But we still need to cover this one:

setup_all do
if use_transactional_test_case?
setup_fixtures
begin_transaction
end
end
teardown_all do
rollback_transaction if use_transactional_test_case?
end

@artem-vavilov
Copy link
Member Author

we run tests 7 ways parallel in drone

This does not really matter, since parallel tests run across isolated worker processes with separate test databases while still executing tests sequentially within each worker

@davidsbailey
Copy link
Member

we run tests 7 ways parallel in drone

This does not really matter, since parallel tests run across isolated worker processes with separate test databases while still executing tests sequentially within each worker

that may be, but the number of separate test databases is also 7. if you add 7 colliding objects to fewer than 7 setup_all methods, then you run the risk that each one pollutes exactly one test db without ever having a collision.

@artem-vavilov
Copy link
Member Author

my recommendation would be to do a similar test to the commit linked above, as proof that we are cleaning up between tests.

The existing TransactionTest already verifies database isolation by establishing a baseline, making a change in one test, and then confirming in a subsequent test that the change was rolled back, but I can add more tests if needed.

Copy link
Member

@davidsbailey davidsbailey left a comment

Choose a reason for hiding this comment

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

Thanks for following up on this Artem! It looks promising, but I think some explicit manual validation is needed to prove that this does the right thing. The following seems like it would be adequate to me:

  1. introduce some colliding object names, as illustrated in previously linked commit
  2. do some print debugging to prove that when running a single test file, the setup_all method in the test file is being executed after the database transaction

Comment on lines -4 to -7
# Setting this to true causes some weird db locking issue, possibly due to
# some writes to the projects table coming from a different connection via
# sequel.
self.use_transactional_test_case = false
Copy link
Member

Choose a reason for hiding this comment

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

please keep a version of the comment explaining that any new failures due to db locking issues could be due to the reason described here

Copy link
Member Author

Choose a reason for hiding this comment

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

Please let me know if this warning is fine, or if you would prefer to keep the original comment:

# Ensures any database changes made in setup_all are rolled back after all tests in the class.
# @warning This keeps a transaction open for the whole test class.
# Writes from other connections, for example Sequel, can wait on locks or hit a MySQL deadlock.
private def setup_all(*args, &block)

Copy link
Member

Choose a reason for hiding this comment

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

there is not much in this setup_all block. how about consolidating it into the setup block instead? then the existing comment should be fine, since that should eliminate the known risk in this particular file.

Copy link
Member Author

@artem-vavilov artem-vavilov Jan 6, 2026

Choose a reason for hiding this comment

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

Do you mean this?

Copy link
Member

Choose a reason for hiding this comment

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

that link didn't work for me, but the updated contents of CodeReviewsControllerTest looks good to me!

@davidsbailey
Copy link
Member

my recommendation would be to do a similar test to the commit linked above, as proof that we are cleaning up between tests.

The existing TransactionTest already verifies database isolation by establishing a baseline, making a change in one test, and then confirming in a subsequent test that the change was rolled back, but I can add more tests if needed.

sorry I missed this earlier comment. I don't see anything in that test which covers the case where the colliding objects are created in setup_all, so it seems like this is covering the use_transactional_tests functionality rather than the now-removed use_transactional_test_case functionality. please let me know if I am missing something there -- if you have automated testing then I do not also need manual verification for the same functionality.

@artem-vavilov
Copy link
Member Author

sorry I missed this earlier comment. I don't see anything in that test which covers the case where the colliding objects are created in setup_all, so it seems like this is covering the use_transactional_tests functionality rather than the now-removed use_transactional_test_case functionality. please let me know if I am missing something there -- if you have automated testing then I do not also need manual verification for the same functionality.

Added some tests: 77994c4

class ActiveSupport::Testing::TransactionalTestCaseTest < ActiveSupport::TestCase
UNIT_NAMES = Array.new(10) {Faker::Lorem.unique.word.downcase}

setup_all do
Copy link
Member

Choose a reason for hiding this comment

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

It looks like one of my comments is not showing up on the PR. Please double check that this call to setup_all runs when you expect it to, because it can sometimes have unexpected behavior when run with describe / it.

Copy link
Member Author

Choose a reason for hiding this comment

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

We already verify that the data and instance variables created in the root class setup_all callback are shared in the describe/context/it blocks, which is expected and desirable behavior. And that they run within a single transaction, meaning no extra transaction is created. Could you please specify what else you would like us to verify?

@artem-vavilov artem-vavilov requested a review from Copilot January 9, 2026 12:15

This comment was marked as off-topic.

@artem-vavilov artem-vavilov merged commit a348065 into staging Jan 9, 2026
6 checks passed
@artem-vavilov artem-vavilov deleted the transactional-tests branch January 9, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants