Skip to content
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

Implement a simple, non-interactive --watch mode #4

Merged
merged 4 commits into from
Feb 19, 2024
Merged

Conversation

mattbrictson
Copy link
Owner

@mattbrictson mattbrictson commented Feb 18, 2024

This PR introduces a --watch mode, inspired by similar features in jest, guard, and retest.

It uses the listen gem to monitor the file system for changes. When changes are detected, mighty_test uses a simple heuristic to locate the test file(s) associated with the changes. This logic is encapsulated in a new FileSystem class.

Tests are run via an event loop. This simplifies testing, as it means the test execution happens synchronously on the main thread, even though the file system changes are detected asynchronously via a background thread managed by the listen gem. The file system changes detected in the listener thread are posted as event to the run loop, which then gets picked up and processed on the next "tick".

The event loop is implemented using Concurrent::MVar, which is essentially a blocking queue of length 1 to which events are posted and then consumed.

The tricky part of implementing this is how to handle ctrl-c. Normally when ctrl-c is pressed, all Ruby threads are interrupted with an Interrupt exception, and all child processed are killed. This includes the background process that is monitoring for file system changes.

I want ctrl-c to interrupt any tests that are currently being run, but not exit the watch process. This will allow a user to interrupt a slow/stuck test and then continue TDD, without having to start the watch command again from scratch.

My solution was to rescue Interrupt when running tests, and then restart the file system listener (since ctrl-c will have killed its background process). This seems to work well.

If a test is not currently running, then ctrl-c works normally and causes the watch process to exit.

@mattbrictson mattbrictson added the ✨ Feature Adds a new feature label Feb 18, 2024
Listen.to(*%w[app lib test].select { |p| Dir.exist?(p) }, relative: true, &).tap(&:start)
end

def find_matching_test_file(path)
Copy link
Owner Author

Choose a reason for hiding this comment

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

🗒️ Implements a simple algorithm that matches e.g. app/models/user.rb to test/models/user_test.rb.

@system_proc = system_proc
end

def run(iterations: :indefinitely)
Copy link
Owner Author

Choose a reason for hiding this comment

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

🗒️ Normally the watcher runs indefinitely, waiting for file system changes. The iterations arg is to support unit testing of this class, so that we can specify an upper limit on how many times the event loop is run.

@mattbrictson mattbrictson merged commit 369b200 into main Feb 19, 2024
6 checks passed
@mattbrictson mattbrictson deleted the watch branch February 19, 2024 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature Adds a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant