-
Notifications
You must be signed in to change notification settings - Fork 28
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
Add access to batches of tests fetched from the Queue API in RSpec, Minitest, Cucumber #252
Conversation
…of tests correctly and its statuses
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.
Does this change create any problems if you run KSP locally and CTRL+c?
Left some comments, please let me know when you've applied what you think is valuable. I'll do a second round then.
@@ -1,5 +1,13 @@ | |||
# Changelog | |||
|
|||
### 7.3.0 | |||
|
|||
* Add access to batches of tests fetched from the Queue API in RSpec, Minitest, Cucumber |
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.
Maybe let's add a link to the docs when it's documented?
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'd like to add an exact example of how to use the new feature in the changelog. The changelog would remain accurate for a given version of the knapsack_pro gem if docs change in the future.
module Store | ||
# Consumers of this class are the gem's users. | ||
# Ensure the API is backward compatible when introducing changes. | ||
class Client |
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.
Client
is confusing to me. What do you think about leaving the public interface on the KnapsackPro::Store
level?
I much prefer KnapsackPro::Store.batches
to KnapsackPro::Store::Client
. The client part is leaking an implementation detail that a server/client exist internally.
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.
Should the Store still be a module or should I convert it to a class?
module Store | ||
class QueueBatchManager | ||
attr_reader :batches | ||
|
||
def initialize | ||
@batches = [] | ||
end | ||
|
||
def add_batch(test_file_paths) | ||
@batches << KnapsackPro::Store::TestBatch.new(test_file_paths) | ||
end | ||
|
||
def last_batch_passed! | ||
@batches.last.send(:passed!) | ||
end | ||
|
||
def last_batch_failed! | ||
@batches.last.send(:failed!) | ||
end | ||
end |
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.
Some ideas in here:
class Queue
include Enumerable
def initialize
@batches = []
end
def each # https://ruby-doc.org/3.2.2/Enumerable.html#module-Enumerable-label-Usage
def add_batch(batch)
@batches << KnapsackPro::Store::TestBatch.new(batch)
end
def mark_batch_passed
current_batch._passed
end
def mark_batch_failed
current_batch._failed
end
private
def current_batch
@batches.last
end
end
end | ||
end | ||
|
||
def self.client |
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 returns the object served by the server. But outside of this file, nobody knows there's a server/client.
What if we call it self.queue
? See also the comments in lib/knapsack_pro/store/queue_batch_manager.rb
for context.
|
||
def self.stop | ||
return if @server_pid.nil? | ||
Process.kill('TERM', @server_pid) |
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 could raise if the pid is not found. Is there any chance it could happen that pid is not a running process? Maybe because of multiple calls to stop
?
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.
Maybe because of multiple calls to stop?
Yes. I wanted our tests (spec/knapsack_pro/store/server_spec.rb
) to be more robust if something fails in the middle or is terminated by the user. It's safe to always call the stop
method (indirectly via the reset
method) before you run a new test case even when process does not exist yet.
Is there any chance it could happen that pid is not a running process?
I hope not. I tested that. It is not possible to kill a child process (the store which is forked process) even with KILL -9. The parent process must be shutdown first. The parent would terminate the child process (the store).
Just in case I handle scenario when the PID does not exist and Errno::ESRCH
is raised.
Co-authored-by: Riccardo <riccardo.odone@gmail.com>
Co-authored-by: Riccardo <riccardo.odone@gmail.com>
This reverts commit d200ad9.
As promised, I'm leaving here some notes for reference since we are closing this PR. I did a partial refactor of the # frozen_string_literal: true
module KnapsackPro
module Store
class Server
extend Forwardable
def self.start
return if @server_pid
assign_available_store_server_uri
@server_pid = fork do
queue = Queue.new
Signal.trap("TERM") { queue.push(nil) }
DRb.start_service(store_server_uri!, new)
queue.pop # wait for TERM
DRb.stop_service
DRb.thread&.join
end
::Kernel.at_exit { stop }
end
def self.client
return @client unless @client.nil?
DRb.start_service
@client = DRbObject.new_with_uri(store_server_uri!)
begin
retries ||= 0
@client.ping
@client
rescue DRb::DRbConnError
wait_seconds = 0.1
retries += wait_seconds
sleep wait_seconds
retry if retries <= 3 # seconds
raise
end
end
def_delegators :@queue_batch_manager, :add_batch, :last_batch_passed!, :last_batch_failed!, :batches
def initialize
@queue_batch_manager = KnapsackPro::Store::QueueBatchManager.new
end
def ping
true
end
private
def self.stop
return if @server_pid.nil?
Process.kill('TERM', @server_pid)
Process.waitpid2(@server_pid)
rescue Errno::ESRCH, Errno::ECHILD
@server_pid = nil
end
def self.store_server_uri
ENV['KNAPSACK_PRO_STORE_SERVER_URI']
end
def self.store_server_uri!
store_server_uri || raise("KNAPSACK_PRO_STORE_SERVER_URI must be set to available DRb port.")
end
# must be set in the main/parent process to make the env var available to the child process
def self.assign_available_store_server_uri
return if store_server_uri
DRb.start_service('druby://localhost:0')
ENV['KNAPSACK_PRO_STORE_SERVER_URI'] = DRb.uri
DRb.stop_service
end
end
class TestableServer < Server # I would move this to the _spec file
def self.reset
stop
ENV['KNAPSACK_PRO_STORE_SERVER_URI'] = nil
@assigned_store_server_uri = nil
@client = nil
end
end
end
end I wanted to propose renaming things, though I'm yet not convinced 100%:
We could have considered using sockets instead of TCP: DRb::DRbUNIXSocket. Depending on the size of the payloads we may have looked into DRb::DRbUndumped. Should have we looked more into GC? https://www.druby.org/sidruby/11-1-dealing-with-gc.html |
Story
Link to the internal story
Description
Applies to RSpec, Minitest, Cucumber in Queue Mode.
User can access info about batches of tests fetched from the Queue API. For example, you can access it within queue hooks like this:
If you take a batch you can check if its tests has been executed and the batch status (at least one failing test case means the batch failed):
Checklist reminder
lib/knapsack_pro/pure/queue/rspec_pure.rb
contains pure functions that are unit tested.lib/knapsack_pro/extensions/rspec_extension.rb
encapsulates calls to RSpec internals and is integration and e2e tested.lib/knapsack_pro/runners/queue/rspec_runner.rb
invokes the pure code and the extension to produce side effects, which are integration and e2e tested.