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

Spawning Clockfile jobs inside tests #38

Open
jensb opened this issue Aug 26, 2023 · 3 comments
Open

Spawning Clockfile jobs inside tests #38

jensb opened this issue Aug 26, 2023 · 3 comments

Comments

@jensb
Copy link

jensb commented Aug 26, 2023

Hi,
I started using ruby-clock in a Rails project and so far I love it, great job!
I have one question though: Is it possible to directly execute specific Clockfile tasks synchronously, as in 'now'?
I think this would make a lot of sense for integration testing. For example,

# app/models/user.rb
# ...
def self.create_remote_users
   where(state: 'pending').each do |user|
      if MyRemoteApi.create_user(...) ; user.activate! ; end
   end
end

# Clockfile
every '1 minute' do
    # ... more minutely jobs
    User.create_remote_users
end

# tests/integration/user_integration_test.rb
# this should create a User account
# A cronjob then checks for new Users and creates a user profile on a remote system too
test 'does create remote user too' do
   post '/signup', params: {...}
   assert_response :success
   RubyClock.execute_tasks_for.every '1 minute'       # or RubyClock.execute_tasks_for.cron '0 0 0 * *' , or ...
   assert_equal 'success', MyRemoteApi.login_as(...)
end

This way, I can not just check if my async remote APi job works well (this would be a unit test) but also if I call it at the right point and if it is included in the correct cronjob.

Is something like this possible with ruby-clock? If not, do you think it makes sense to implement it?

@jjb
Copy link
Owner

jjb commented Aug 26, 2023

Thanks for the feedback and ideas, i've wanted something similar myself!

here's something you can try. it's hacky but i think will work, and then
you can give me feedback on the API/ergonomics. when you have something you
like, i'll work a nicer api into the next release.

just put this code directly into your test. no need for invoking the clock executable

require 'ruby-clock'
require "ruby-clock/dsl"
RubyClock.detect_and_load_rails_app
require 'rufus_monkeypatch'
RubyClock.instance.listen_to_signals
RubyClock.instance.prepare_rake
RubyClock.instance.schedule.pause
RubyClock.instance.add_rails_executor_to_around_actions
# above is the beginning of exe/clock

# load your Clockfile
load 'Clockfile'


# Now, everything is loaded and paused. You can access all jobs with this.
RubyClock.instance.schedule

# You can iterate through them and check the identifier.
# read about names/identifiers/slugs in the readme
RubyClock.instance.schedule.jobs.each do |j|
  puts j.name
  puts j.identifier
  puts j.slug

  j.call
end

@jensb
Copy link
Author

jensb commented Aug 27, 2023

Thank you, this actually worked on the first attempt! Here's what I did.

  1. I put your above code until the load Clockfile into a test/test_helper_rubyclock.rb.
  2. I added a function to select a single clockjob to this file:
    def run_clockjob(name) = RubyClock.instance.schedule.jobs.find {|j| j.name == name }.call
  3. I require this file in all my integration test files
  4. I gave all my clockjobs explicit names (I found no other safe way to identify them).
  5. When I want to run a clockjob inside one of my tests, I call
    run_clockjob('every_hour')

This also works on the console:

# Clockfile
using RubyClock::DSL
every '1 minute'  , name: 'every_1min' do Rails.logger.info "Clockfile is active at #{Time.now}" end
$ rails c
Loading development environment (Rails 7.0.7.2)

rb(main):001:0> load 'test/test_helper_clock.rb'
Detected rails app has been loaded.
RUBY_CLOCK_SHUTDOWN_WAIT_SECONDS is set to 29
=> true

irb(main):002:0> RubyClock.instance.schedule.jobs.find {|j| j.name == 'every_1min' }.call
Clockfile is active at 2023-08-27 21:43:04 +0200
=> 49

Maybe we can package this into a require 'ruby-clock/tests' for tests, and then get something like

require 'ruby-clock/tests'
test "see if we can run a clockjob inside here" do
   User.create(...)
   RubyClock.run('create_remote_accounts')     # short, easy, to the point DSL
   assert RemoteApi.login_as(...)
end

What do you think? Also, will this work when calling jobs twice or running tests in parallel? (Rake has issues here)

@jjb
Copy link
Owner

jjb commented Aug 29, 2023

Nice!! Thanks so much for sharing your code, I will use this to guide me for the next release.

I gave all my clockjobs explicit names (I found no other safe way to identify them)

in case you missed it, you can check in CI that your jobs all have unique slugs, and then you can find by slug instead of name. https://github.com/jjb/ruby-clock/#testing --check-slug-uniqueness

will this work when calling jobs twice or running tests in parallel? (Rake has issues here)

regarding rake, you can use the safest invocation method, rake, to ensure the jobs don't overlap. see the code and article linked in this section https://github.com/jjb/ruby-clock/#rake-tasks

do you also mean at the rufus-scheduler level... i think if your job is threadsafe (instance variables directly in the block are almost certainly safe... but maybe it accesses other classes that aren't threadsafe), then you should be good. let me know if you meant something else.

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

No branches or pull requests

2 participants