diff --git a/Gemfile.lock b/Gemfile.lock index 2de5a7f..6ded98f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -35,6 +35,8 @@ GEM docile (1.1.5) domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) + factory_girl (4.5.0) + activesupport (>= 3.0.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) faraday_middleware (0.10.0) @@ -92,6 +94,7 @@ DEPENDENCIES codeclimate-test-reporter coursemology-evaluator! coveralls + factory_girl rake rspec simplecov diff --git a/coursemology-evaluator.gemspec b/coursemology-evaluator.gemspec index ea38ee6..87ba76b 100644 --- a/coursemology-evaluator.gemspec +++ b/coursemology-evaluator.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' + spec.add_development_dependency 'factory_girl' spec.add_development_dependency 'simplecov' spec.add_development_dependency 'coveralls' spec.add_development_dependency 'codeclimate-test-reporter' diff --git a/lib/coursemology/evaluator/cli.rb b/lib/coursemology/evaluator/cli.rb index 30e7f46..69eda07 100644 --- a/lib/coursemology/evaluator/cli.rb +++ b/lib/coursemology/evaluator/cli.rb @@ -1,7 +1,7 @@ require 'optparse' class Coursemology::Evaluator::CLI - Options = Struct.new(:host, :api_token, :api_user_email) + Options = Struct.new(:host, :api_token, :api_user_email, :one_shot) def self.start(argv) new.start(argv) @@ -15,7 +15,7 @@ def run(argv) options = optparse!(argv) Coursemology::Evaluator::Client.initialize(options.host, options.api_user_email, options.api_token) - Coursemology::Evaluator::Client.new.run + Coursemology::Evaluator::Client.new(options.one_shot).run end private @@ -39,6 +39,10 @@ def optparse!(argv) # rubocop:disable Metrics/MethodLength parser.on('-uUSER', '--api-user-email=USER') do |user| options.api_user_email = user end + + parser.on('-o', '--one-shot') do + options.one_shot = true + end end option_parser.parse!(argv) diff --git a/lib/coursemology/evaluator/client.rb b/lib/coursemology/evaluator/client.rb index c0c3bb7..82f1326 100644 --- a/lib/coursemology/evaluator/client.rb +++ b/lib/coursemology/evaluator/client.rb @@ -7,9 +7,50 @@ def self.initialize(host, api_user_email, api_token) Coursemology::Evaluator::Models::Base.initialize end - def initialize + # @param [Boolean] one_shot If the client should only fire one request. + def initialize(one_shot = false) + @terminate = one_shot end def run + Signal.trap('SIGTERM', method(:on_sig_term)) + + loop do + allocate_evaluations + break if @terminate + + sleep(1.minute) + end + end + + private + + # Requests evaluations from the server. + def allocate_evaluations + evaluations = Coursemology::Evaluator::Models::ProgrammingEvaluation.allocate + on_allocate(evaluations) + end + + # The callback for handling an array of allocated evaluations. + # + # @param [Array] evaluations The + # evaluations retrieved from the server. + def on_allocate(evaluations) + evaluations.each do |evaluation| + on_evaluation(evaluation) + end + end + + # The callback for handling an evaluation. + # + # @param [Coursemology::Evaluator::Models::ProgrammingEvaluation] evaluation The evaluation + # retrieved from the server. + def on_evaluation(evaluation) + evaluation.save + end + + # The callback for handling SIGTERM sent to the process. + def on_sig_term + @terminate = true end end diff --git a/spec/coursemology/evaluator/cli_spec.rb b/spec/coursemology/evaluator/cli_spec.rb index bccb8d5..f1e3f56 100644 --- a/spec/coursemology/evaluator/cli_spec.rb +++ b/spec/coursemology/evaluator/cli_spec.rb @@ -1,9 +1,11 @@ RSpec.describe Coursemology::Evaluator::CLI do let(:host) { 'http://localhost:3000' } + let!(:original_api_token) { Coursemology::Evaluator::Models::Base.api_token } let(:api_token) { 'abcd' } let(:api_user_email) { 'test@example.org' } let(:argv) do - ["--host=#{host}", "--api-token=#{api_token}", "--api-user-email=#{api_user_email}"] + ["--host=#{host}", "--api-token=#{api_token}", "--api-user-email=#{api_user_email}", + '--one-shot'] end describe Coursemology::Evaluator::CLI::Options do @@ -22,22 +24,27 @@ describe '#start' do subject { Coursemology::Evaluator::CLI.new } it 'calls #run' do - expect(subject).to receive(:run).and_call_original + expect(subject).to receive(:run) subject.start(argv) end end describe '#run' do + let(:api_token) { original_api_token } subject { Coursemology::Evaluator::CLI.new.run(argv) } it 'creates a client' do expect(Coursemology::Evaluator::Client).to receive(:new).and_call_original - subject + VCR.use_cassette('client/no_pending_evaluations') do + subject + end end it 'runs the client' do expect(Coursemology::Evaluator::Client).to receive_message_chain(:new, :run) - subject + VCR.use_cassette('client/no_pending_evaluations') do + subject + end end end end diff --git a/spec/coursemology/evaluator/client_spec.rb b/spec/coursemology/evaluator/client_spec.rb index c28fd89..fac52da 100644 --- a/spec/coursemology/evaluator/client_spec.rb +++ b/spec/coursemology/evaluator/client_spec.rb @@ -15,4 +15,42 @@ end end end + + describe '#run' do + it 'loops until @terminate is set' do + expect(subject).to receive(:allocate_evaluations).at_least(:once) + allow(subject).to receive(:sleep) do + sleep(0.1.seconds) + end + + Thread.new { subject.instance_variable_set(:@terminate, true) } + subject.run + end + end + + describe '#allocate_evaluations' do + context 'when an evaluation is provided' do + let(:dummy_evaluation) { build(:programming_evaluation) } + before do + expect(Coursemology::Evaluator::Models::ProgrammingEvaluation).to \ + receive(:allocate).and_return([dummy_evaluation]) + end + + it 'calls #on_allocate with the evaluation' do + expect(subject).to receive(:on_allocate).with([dummy_evaluation]).and_call_original + subject.send(:allocate_evaluations) + end + end + end + + describe '#on_sig_term' do + it 'terminates the loop' do + expect(subject).to receive(:allocate_evaluations).at_least(:once) + allow(subject).to receive(:sleep) do + sleep(0.1.seconds) + end + Thread.new { subject.send(:on_sig_term) } + subject.run + end + end end diff --git a/spec/factories/programming_evaluations.rb b/spec/factories/programming_evaluations.rb new file mode 100644 index 0000000..c173364 --- /dev/null +++ b/spec/factories/programming_evaluations.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :programming_evaluation, class: Coursemology::Evaluator::Models::ProgrammingEvaluation do + id 1 + language 'Polyglot::Language::Python::Python2Point7' + memory_limit 32 + time_limit 5 + end +end diff --git a/spec/fixtures/vcr/cassettes/client/no_pending_evaluations.yml b/spec/fixtures/vcr/cassettes/client/no_pending_evaluations.yml new file mode 100644 index 0000000..5f9eb7e --- /dev/null +++ b/spec/fixtures/vcr/cassettes/client/no_pending_evaluations.yml @@ -0,0 +1,58 @@ +--- +http_interactions: +- request: + method: post + uri: http://localhost:3000/courses/assessment/programming_evaluations/allocate + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - ActiveRestClient/1.2.0 + Connection: + - Keep-Alive + Accept: + - application/hal+json, application/json;q=0.5 + X-User-Email: + - test@example.org + X-User-Token: + - YOUR_TOKEN_HERE + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 200 + message: + headers: + x-frame-options: + - SAMEORIGIN + x-xss-protection: + - 1; mode=block + x-content-type-options: + - nosniff + content-type: + - application/json; charset=utf-8 + cache-control: + - no-store, must-revalidate, private, max-age=0 + x-request-id: + - 56109927-259f-413d-8dbb-95f02dc1e3e7 + x-runtime: + - '0.580646' + x-miniprofiler-ids: + - '["g61697empnv3cyhwvvlu","sv2daht3quq3ti4jvvg5","uv84uouoadr9cjoxx5df","vzn03ouf207zi8ixeyqu","ezc78yubvk6fv8mnklhu","5rc1ta39wdyj0fv8dh6z","w05805bb6qbjhekuariy","8wynqvhb8fmrem7vo261","vr7hm3zpbq6ftrubgyqj","ow31gd85uwp55l8a5vnq"]' + server: + - WEBrick/1.3.1 (Ruby/2.2.4/2015-10-06) + date: + - Tue, 22 Dec 2015 01:22:30 GMT + content-length: + - '2' + connection: + - Keep-Alive + set-cookie: + - __profilin=p%3Dt; path=/, __profilin=p%3Dt; path=/, __profilin=p%3Dt; path=/ + body: + encoding: UTF-8 + string: "[]" + http_version: + recorded_at: Tue, 22 Dec 2015 01:22:30 GMT +recorded_with: VCR 3.0.0 diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb new file mode 100644 index 0000000..c0759fa --- /dev/null +++ b/spec/support/factory_girl.rb @@ -0,0 +1,9 @@ +require 'factory_girl' + +RSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods + + config.before(:suite) do + FactoryGirl.find_definitions + end +end