2 require 'helpers/git_test_helper'
4 class Arvados::V1::JobReuseControllerTest < ActionController::TestCase
5 fixtures :repositories, :users, :jobs, :links, :collections
7 # See git_setup.rb for the commit log for test.git.tar
11 @controller = Arvados::V1::JobsController.new
12 authorize_with :active
15 test "reuse job with no_reuse=false" do
19 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
22 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
26 assert_response :success
27 assert_not_nil assigns(:object)
28 new_job = JSON.parse(@response.body)
29 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
30 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
33 test "reuse job with find_or_create=true" do
37 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
40 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
46 assert_response :success
47 assert_not_nil assigns(:object)
48 new_job = JSON.parse(@response.body)
49 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
50 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
53 test "reuse job with symbolic script_version" do
57 script_version: "tag1",
60 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
66 assert_response :success
67 assert_not_nil assigns(:object)
68 new_job = JSON.parse(@response.body)
69 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
70 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
73 test "do not reuse job because no_reuse=true" do
78 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
81 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
86 assert_response :success
87 assert_not_nil assigns(:object)
88 new_job = JSON.parse(@response.body)
89 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
90 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
93 test "do not reuse job because find_or_create=false" do
97 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
100 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
104 find_or_create: false
106 assert_response :success
107 assert_not_nil assigns(:object)
108 new_job = JSON.parse(@response.body)
109 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
110 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
113 test "test_cannot_reuse_job_no_output" do
117 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
120 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
124 assert_response :success
125 assert_not_nil assigns(:object)
126 new_job = JSON.parse(@response.body)
127 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykppp', new_job['uuid']
130 test "test_reuse_job_range" do
134 minimum_script_version: "tag1",
135 script_version: "master",
138 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
142 assert_response :success
143 assert_not_nil assigns(:object)
144 new_job = JSON.parse(@response.body)
145 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
146 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
149 test "cannot_reuse_job_no_minimum_given_so_must_use_specified_commit" do
153 script_version: "master",
156 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
160 assert_response :success
161 assert_not_nil assigns(:object)
162 new_job = JSON.parse(@response.body)
163 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
164 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
167 test "test_cannot_reuse_job_different_input" do
171 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
174 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
178 assert_response :success
179 assert_not_nil assigns(:object)
180 new_job = JSON.parse(@response.body)
181 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
182 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
185 test "test_cannot_reuse_job_different_version" do
189 script_version: "master",
192 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
196 assert_response :success
197 assert_not_nil assigns(:object)
198 new_job = JSON.parse(@response.body)
199 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
200 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
203 test "test_can_reuse_job_submitted_nondeterministic" do
207 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
210 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
213 nondeterministic: true
215 assert_response :success
216 assert_not_nil assigns(:object)
217 new_job = JSON.parse(@response.body)
218 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
219 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
222 test "test_cannot_reuse_job_past_nondeterministic" do
226 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
229 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
233 assert_response :success
234 assert_not_nil assigns(:object)
235 new_job = JSON.parse(@response.body)
236 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykyyy', new_job['uuid']
237 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
240 test "test_cannot_reuse_job_no_permission" do
241 authorize_with :spectator
245 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
248 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
252 assert_response :success
253 assert_not_nil assigns(:object)
254 new_job = JSON.parse(@response.body)
255 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
256 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
259 test "test_cannot_reuse_job_excluded" do
263 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
264 script_version: "master",
266 exclude_script_versions: ["tag1"],
268 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
272 assert_response :success
273 assert_not_nil assigns(:object)
274 new_job = JSON.parse(@response.body)
275 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
276 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
279 test "cannot reuse job with find_or_create but excluded version" do
283 script_version: "master",
286 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
290 find_or_create: true,
291 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
292 exclude_script_versions: ["tag1"],
294 assert_response :success
295 assert_not_nil assigns(:object)
296 new_job = JSON.parse(@response.body)
297 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
298 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
302 'repository' => ['=', 'foo'],
303 'script' => ['=', 'hash'],
304 'script_version' => ['in git', 'master'],
305 'docker_image_locator' => ['=', nil],
308 def filters_from_hash(hash)
309 hash.each_pair.map { |name, filter| [name] + filter }
312 test "can reuse a Job based on filters" do
313 filter_h = BASE_FILTERS.
314 merge('script_version' => ['in git', 'tag1'])
318 script_version: "master",
321 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
325 filters: filters_from_hash(filter_h),
326 find_or_create: true,
328 assert_response :success
329 assert_not_nil assigns(:object)
330 new_job = JSON.parse(@response.body)
331 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
332 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
335 test "can not reuse a Job based on filters" do
336 filter_a = filters_from_hash(BASE_FILTERS.reject { |k| k == 'script_version' })
337 filter_a += [["script_version", "in git",
338 "31ce37fe365b3dc204300a3e4c396ad333ed0556"],
339 ["script_version", "not in git", ["tag1"]]]
343 script_version: "master",
346 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
351 find_or_create: true,
353 assert_response :success
354 assert_not_nil assigns(:object)
355 new_job = JSON.parse(@response.body)
356 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
357 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
360 test "can not reuse a Job based on arbitrary filters" do
361 filter_h = BASE_FILTERS.
362 merge("created_at" => ["<", "2010-01-01T00:00:00Z"])
366 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
369 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
373 filters: filters_from_hash(filter_h),
374 find_or_create: true,
376 assert_response :success
377 assert_not_nil assigns(:object)
378 new_job = JSON.parse(@response.body)
379 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
380 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
383 test "can reuse a Job with a Docker image" do
387 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
390 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
393 runtime_constraints: {
394 docker_image: 'arvados/apitestfixture',
397 find_or_create: true,
399 assert_response :success
400 new_job = assigns(:object)
401 assert_not_nil new_job
402 target_job = jobs(:previous_docker_job_run)
403 [:uuid, :script_version, :docker_image_locator].each do |attr|
404 assert_equal(target_job.send(attr), new_job.send(attr))
408 test "can reuse a Job with a Docker image hash filter" do
409 filter_h = BASE_FILTERS.
410 merge("script_version" =>
411 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
412 "docker_image_locator" =>
413 ["in docker", links(:docker_image_collection_hash).name])
417 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
420 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
424 filters: filters_from_hash(filter_h),
425 find_or_create: true,
427 assert_response :success
428 new_job = assigns(:object)
429 assert_not_nil new_job
430 target_job = jobs(:previous_docker_job_run)
431 [:uuid, :script_version, :docker_image_locator].each do |attr|
432 assert_equal(target_job.send(attr), new_job.send(attr))
436 test "reuse Job with Docker image repo+tag" do
437 filter_h = BASE_FILTERS.
438 merge("script_version" =>
439 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
440 "docker_image_locator" =>
441 ["in docker", links(:docker_image_collection_tag2).name])
445 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
448 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
452 filters: filters_from_hash(filter_h),
453 find_or_create: true,
455 assert_response :success
456 new_job = assigns(:object)
457 assert_not_nil new_job
458 target_job = jobs(:previous_docker_job_run)
459 [:uuid, :script_version, :docker_image_locator].each do |attr|
460 assert_equal(target_job.send(attr), new_job.send(attr))
464 test "new job with unknown Docker image filter" do
465 filter_h = BASE_FILTERS.
466 merge("docker_image_locator" => ["in docker", "_nonesuchname_"])
470 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
473 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
477 filters: filters_from_hash(filter_h),
478 find_or_create: true,
480 assert_response :success
481 new_job = assigns(:object)
482 assert_not_nil new_job
483 assert_not_equal(jobs(:previous_docker_job_run).uuid, new_job.uuid)
486 ["repository", "script"].each do |skip_key|
487 test "missing #{skip_key} filter raises an error" do
488 filter_a = filters_from_hash(BASE_FILTERS.reject { |k| k == skip_key })
492 script_version: "master",
495 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
500 find_or_create: true,
502 assert_includes(405..599, @response.code.to_i,
503 "bad status code with missing #{skip_key} filter")
507 test "find Job with script version range" do
508 get :index, filters: [["repository", "=", "foo"],
509 ["script", "=", "hash"],
510 ["script_version", "in git", "tag1"]]
511 assert_response :success
512 assert_not_nil assigns(:objects)
513 assert_includes(assigns(:objects).map { |job| job.uuid },
514 jobs(:previous_job_run).uuid)
517 test "find Job with script version range exclusions" do
518 get :index, filters: [["repository", "=", "foo"],
519 ["script", "=", "hash"],
520 ["script_version", "not in git", "tag1"]]
521 assert_response :success
522 assert_not_nil assigns(:objects)
523 refute_includes(assigns(:objects).map { |job| job.uuid },
524 jobs(:previous_job_run).uuid)
527 test "find Job with Docker image range" do
528 get :index, filters: [["docker_image_locator", "in docker",
529 "arvados/apitestfixture"]]
530 assert_response :success
531 assert_not_nil assigns(:objects)
532 assert_includes(assigns(:objects).map { |job| job.uuid },
533 jobs(:previous_docker_job_run).uuid)
534 refute_includes(assigns(:objects).map { |job| job.uuid },
535 jobs(:previous_job_run).uuid)
538 test "find Job with Docker image using reader tokens" do
539 authorize_with :inactive
541 filters: [["docker_image_locator", "in docker",
542 "arvados/apitestfixture"]],
543 reader_tokens: [api_token(:active)],
545 assert_response :success
546 assert_not_nil assigns(:objects)
547 assert_includes(assigns(:objects).map { |job| job.uuid },
548 jobs(:previous_docker_job_run).uuid)
549 refute_includes(assigns(:objects).map { |job| job.uuid },
550 jobs(:previous_job_run).uuid)
553 test "'in docker' filter accepts arrays" do
554 get :index, filters: [["docker_image_locator", "in docker",
555 ["_nonesuchname_", "arvados/apitestfixture"]]]
556 assert_response :success
557 assert_not_nil assigns(:objects)
558 assert_includes(assigns(:objects).map { |job| job.uuid },
559 jobs(:previous_docker_job_run).uuid)
560 refute_includes(assigns(:objects).map { |job| job.uuid },
561 jobs(:previous_job_run).uuid)
564 test "'not in docker' filter accepts arrays" do
565 get :index, filters: [["docker_image_locator", "not in docker",
566 ["_nonesuchname_", "arvados/apitestfixture"]]]
567 assert_response :success
568 assert_not_nil assigns(:objects)
569 assert_includes(assigns(:objects).map { |job| job.uuid },
570 jobs(:previous_job_run).uuid)
571 refute_includes(assigns(:objects).map { |job| job.uuid },
572 jobs(:previous_docker_job_run).uuid)
575 def create_foo_hash_job_params(params)
576 if not params.has_key?(:find_or_create)
577 params[:find_or_create] = true
579 job_attrs = params.delete(:job) || {}
582 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
585 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
592 def check_new_job_created_from(params)
593 start_time = Time.now
594 post(:create, create_foo_hash_job_params(params))
595 assert_response :success
596 new_job = assigns(:object)
597 assert_not_nil new_job
598 assert_operator(start_time, :<=, new_job.created_at)
602 def check_errors_from(params)
603 post(:create, create_foo_hash_job_params(params))
604 assert_includes(405..499, @response.code.to_i)
605 errors = json_response.fetch("errors", [])
606 assert(errors.any?, "no errors assigned from #{params}")
607 refute(errors.any? { |msg| msg =~ /^#<[A-Za-z]+: / },
608 "errors include raw exception")
612 # 1de84a8 is on the b1 branch, after master's tip.
613 test "new job created from unsatisfiable minimum version filter" do
614 filter_h = BASE_FILTERS.merge("script_version" => ["in git", "1de84a8"])
615 check_new_job_created_from(filters: filters_from_hash(filter_h))
618 test "new job created from unsatisfiable minimum version parameter" do
619 check_new_job_created_from(minimum_script_version: "1de84a8")
622 test "new job created from unsatisfiable minimum version attribute" do
623 check_new_job_created_from(job: {minimum_script_version: "1de84a8"})
626 test "graceful error from nonexistent minimum version filter" do
627 filter_h = BASE_FILTERS.merge("script_version" =>
628 ["in git", "__nosuchbranch__"])
629 errors = check_errors_from(filters: filters_from_hash(filter_h))
630 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
631 "bad refspec not mentioned in error message")
634 test "graceful error from nonexistent minimum version parameter" do
635 errors = check_errors_from(minimum_script_version: "__nosuchbranch__")
636 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
637 "bad refspec not mentioned in error message")
640 test "graceful error from nonexistent minimum version attribute" do
641 errors = check_errors_from(job: {minimum_script_version: "__nosuchbranch__"})
642 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
643 "bad refspec not mentioned in error message")