Merge branch '8016-crunchrun-crunchstat'
[arvados.git] / apps / workbench / test / controllers / pipeline_instances_controller_test.rb
1 require 'test_helper'
2
3 class PipelineInstancesControllerTest < ActionController::TestCase
4   include PipelineInstancesHelper
5
6   def create_instance_long_enough_to(instance_attrs={})
7     # create 'two_part' pipeline with the given instance attributes
8     pt_fixture = api_fixture('pipeline_templates')['two_part']
9     post :create, {
10       pipeline_instance: instance_attrs.merge({
11         pipeline_template_uuid: pt_fixture['uuid']
12       }),
13       format: :json
14     }, session_for(:active)
15     assert_response :success
16     pi_uuid = assigns(:object).uuid
17     assert_not_nil assigns(:object)
18
19     # yield
20     yield pi_uuid, pt_fixture
21
22     # delete the pipeline instance
23     use_token :active
24     PipelineInstance.where(uuid: pi_uuid).first.destroy
25   end
26
27   test "pipeline instance components populated after create" do
28     create_instance_long_enough_to do |new_instance_uuid, template_fixture|
29       assert_equal(template_fixture['components'].to_json,
30                    assigns(:object).components.to_json)
31     end
32   end
33
34   test "can render pipeline instance with tagged collections" do
35     # Make sure to pass in a tagged collection to test that part of the rendering behavior.
36     get(:show,
37         {id: api_fixture("pipeline_instances")["pipeline_with_tagged_collection_input"]["uuid"]},
38         session_for(:active))
39     assert_response :success
40   end
41
42   test "update script_parameters one at a time using merge param" do
43       template_fixture = api_fixture('pipeline_templates')['two_part']
44       post :update, {
45         id: api_fixture("pipeline_instances")["pipeline_to_merge_params"]["uuid"],
46         pipeline_instance: {
47           components: {
48             "part-two" => {
49               script_parameters: {
50                 integer_with_value: {
51                   value: 9
52                 },
53                 plain_string: {
54                   value: 'quux'
55                 },
56               }
57             }
58           }
59         },
60         merge: true,
61         format: :json
62       }, session_for(:active)
63       assert_response :success
64       assert_not_nil assigns(:object)
65       orig_params = template_fixture['components']['part-two']['script_parameters']
66       new_params = assigns(:object).components[:'part-two'][:script_parameters]
67       orig_params.keys.each do |k|
68         unless %w(integer_with_value plain_string).index(k)
69           assert_equal orig_params[k].to_json, new_params[k.to_sym].to_json
70         end
71       end
72   end
73
74   test "component rendering copes with unexpected components format" do
75     get(:show,
76         {id: api_fixture("pipeline_instances")["components_is_jobspec"]["uuid"]},
77         session_for(:active))
78     assert_response :success
79   end
80
81   test "dates in JSON components are parsed" do
82     get(:show,
83         {id: api_fixture('pipeline_instances')['has_component_with_completed_jobs']['uuid']},
84         session_for(:active))
85     assert_response :success
86     assert_not_nil assigns(:object)
87     assert_not_nil assigns(:object).components[:foo][:job]
88     start_at = assigns(:object).components[:foo][:job][:started_at]
89     start_at = Time.parse(start_at) if (start_at.andand.class == String)
90     assert start_at.is_a? Time
91     finished_at = assigns(:object).components[:foo][:job][:started_at]
92     finished_at = Time.parse(finished_at) if (finished_at.andand.class == String)
93     assert finished_at.is_a? Time
94   end
95
96   # The next two tests ensure that a pipeline instance can be copied
97   # when the template has components that do not exist in the
98   # instance (ticket #4000).
99
100   test "copy pipeline instance with components=use_latest" do
101     post(:copy,
102          {
103            id: api_fixture('pipeline_instances')['pipeline_with_newer_template']['uuid'],
104            components: 'use_latest',
105            script: 'use_latest',
106            pipeline_instance: {
107              state: 'RunningOnServer'
108            }
109          },
110          session_for(:active))
111     assert_response 302
112     assert_not_nil assigns(:object)
113
114     # Component 'foo' has script parameters only in the pipeline instance.
115     # Component 'bar' is present only in the pipeline_template.
116     # Test that the copied pipeline instance includes parameters for
117     # component 'foo' from the source instance, and parameters for
118     # component 'bar' from the source template.
119     #
120     assert_not_nil assigns(:object).components[:foo]
121     foo = assigns(:object).components[:foo]
122     assert_not_nil foo[:script_parameters]
123     assert_not_nil foo[:script_parameters][:input]
124     assert_equal 'foo instance input', foo[:script_parameters][:input][:title]
125
126     assert_not_nil assigns(:object).components[:bar]
127     bar = assigns(:object).components[:bar]
128     assert_not_nil bar[:script_parameters]
129     assert_not_nil bar[:script_parameters][:input]
130     assert_equal 'bar template input', bar[:script_parameters][:input][:title]
131   end
132
133   test "copy pipeline instance on newer template works with script=use_same" do
134     post(:copy,
135          {
136            id: api_fixture('pipeline_instances')['pipeline_with_newer_template']['uuid'],
137            components: 'use_latest',
138            script: 'use_same',
139            pipeline_instance: {
140              state: 'RunningOnServer'
141            }
142          },
143          session_for(:active))
144     assert_response 302
145     assert_not_nil assigns(:object)
146
147     # Test that relevant component parameters were copied from both
148     # the source instance and source template, respectively (see
149     # previous test)
150     #
151     assert_not_nil assigns(:object).components[:foo]
152     foo = assigns(:object).components[:foo]
153     assert_not_nil foo[:script_parameters]
154     assert_not_nil foo[:script_parameters][:input]
155     assert_equal 'foo instance input', foo[:script_parameters][:input][:title]
156
157     assert_not_nil assigns(:object).components[:bar]
158     bar = assigns(:object).components[:bar]
159     assert_not_nil bar[:script_parameters]
160     assert_not_nil bar[:script_parameters][:input]
161     assert_equal 'bar template input', bar[:script_parameters][:input][:title]
162   end
163
164   test "generate graph" do
165
166     use_token 'admin'
167
168     pipeline_for_graph = {
169       state: 'Complete',
170       uuid: 'zzzzz-d1hrv-9fm8l10i9z2kqc9',
171       components: {
172         stage1: {
173           repository: 'foo',
174           script: 'hash',
175           script_version: 'master',
176           job: {uuid: 'zzzzz-8i9sb-graphstage10000'},
177           output_uuid: 'zzzzz-4zz18-bv31uwvy3neko22'
178         },
179         stage2: {
180           repository: 'foo',
181           script: 'hash2',
182           script_version: 'master',
183           script_parameters: {
184             input: 'fa7aeb5140e2848d39b416daeef4ffc5+45'
185           },
186           job: {uuid: 'zzzzz-8i9sb-graphstage20000'},
187           output_uuid: 'zzzzz-4zz18-uukreo9rbgwsujx'
188         }
189       }
190     }
191
192     @controller.params['tab_pane'] = "Graph"
193     provenance, pips = @controller.graph([pipeline_for_graph])
194
195     graph_test_collection1 = find_fixture Collection, "graph_test_collection1"
196     stage1 = find_fixture Job, "graph_stage1"
197     stage2 = find_fixture Job, "graph_stage2"
198
199     ['component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage1',
200      'component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage2',
201      stage1.uuid,
202      stage2.uuid,
203      stage1.output,
204      stage2.output,
205      pipeline_for_graph[:components][:stage1][:output_uuid],
206      pipeline_for_graph[:components][:stage2][:output_uuid]
207     ].each do |k|
208
209       assert_not_nil provenance[k], "Expected key #{k} in provenance set"
210       assert_equal 1, pips[k], "Expected key #{k} in pips set" if !k.start_with? "component_"
211     end
212
213     prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
214         :request => RequestDuck,
215         :all_script_parameters => true,
216         :combine_jobs => :script_and_version,
217         :pips => pips,
218         :only_components => true }
219
220     stage1_id = "#{stage1[:script]}_#{stage1[:script_version]}_#{Digest::MD5.hexdigest(stage1[:script_parameters].to_json)}"
221     stage2_id = "#{stage2[:script]}_#{stage2[:script_version]}_#{Digest::MD5.hexdigest(stage2[:script_parameters].to_json)}"
222
223     stage1_out = stage1[:output].gsub('+','\\\+')
224
225     assert_match /#{stage1_id}&#45;&gt;#{stage1_out}/, prov_svg
226
227     assert_match /#{stage1_out}&#45;&gt;#{stage2_id}/, prov_svg
228
229   end
230
231   test "generate graph compare" do
232
233     use_token 'admin'
234
235     pipeline_for_graph1 = {
236       state: 'Complete',
237       uuid: 'zzzzz-d1hrv-9fm8l10i9z2kqc9',
238       components: {
239         stage1: {
240           repository: 'foo',
241           script: 'hash',
242           script_version: 'master',
243           job: {uuid: 'zzzzz-8i9sb-graphstage10000'},
244           output_uuid: 'zzzzz-4zz18-bv31uwvy3neko22'
245         },
246         stage2: {
247           repository: 'foo',
248           script: 'hash2',
249           script_version: 'master',
250           script_parameters: {
251             input: 'fa7aeb5140e2848d39b416daeef4ffc5+45'
252           },
253           job: {uuid: 'zzzzz-8i9sb-graphstage20000'},
254           output_uuid: 'zzzzz-4zz18-uukreo9rbgwsujx'
255         }
256       }
257     }
258
259     pipeline_for_graph2 = {
260       state: 'Complete',
261       uuid: 'zzzzz-d1hrv-9fm8l10i9z2kqc0',
262       components: {
263         stage1: {
264           repository: 'foo',
265           script: 'hash',
266           script_version: 'master',
267           job: {uuid: 'zzzzz-8i9sb-graphstage10000'},
268           output_uuid: 'zzzzz-4zz18-bv31uwvy3neko22'
269         },
270         stage2: {
271           repository: 'foo',
272           script: 'hash2',
273           script_version: 'master',
274           script_parameters: {
275           },
276           job: {uuid: 'zzzzz-8i9sb-graphstage30000'},
277           output_uuid: 'zzzzz-4zz18-uukreo9rbgwsujj'
278         }
279       }
280     }
281
282     @controller.params['tab_pane'] = "Graph"
283     provenance, pips = @controller.graph([pipeline_for_graph1, pipeline_for_graph2])
284
285     collection1 = find_fixture Collection, "graph_test_collection1"
286
287     stage1 = find_fixture Job, "graph_stage1"
288     stage2 = find_fixture Job, "graph_stage2"
289     stage3 = find_fixture Job, "graph_stage3"
290
291     [['component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage1', nil],
292      ['component_zzzzz-d1hrv-9fm8l10i9z2kqc9_stage2', nil],
293      ['component_zzzzz-d1hrv-9fm8l10i9z2kqc0_stage1', nil],
294      ['component_zzzzz-d1hrv-9fm8l10i9z2kqc0_stage2', nil],
295      [stage1.uuid, 3],
296      [stage2.uuid, 1],
297      [stage3.uuid, 2],
298      [stage1.output, 3],
299      [stage2.output, 1],
300      [stage3.output, 2],
301      [pipeline_for_graph1[:components][:stage1][:output_uuid], 3],
302      [pipeline_for_graph1[:components][:stage2][:output_uuid], 1],
303      [pipeline_for_graph2[:components][:stage2][:output_uuid], 2]
304     ].each do |k|
305       assert_not_nil provenance[k[0]], "Expected key #{k[0]} in provenance set"
306       assert_equal k[1], pips[k[0]], "Expected key #{k} in pips" if !k[0].start_with? "component_"
307     end
308
309     prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
310         :request => RequestDuck,
311         :all_script_parameters => true,
312         :combine_jobs => :script_and_version,
313         :pips => pips,
314         :only_components => true }
315
316     collection1_id = collection1.portable_data_hash.gsub('+','\\\+')
317
318     stage2_id = "#{stage2[:script]}_#{stage2[:script_version]}_#{Digest::MD5.hexdigest(stage2[:script_parameters].to_json)}"
319     stage3_id = "#{stage3[:script]}_#{stage3[:script_version]}_#{Digest::MD5.hexdigest(stage3[:script_parameters].to_json)}"
320
321     stage2_out = stage2[:output].gsub('+','\\\+')
322     stage3_out = stage3[:output].gsub('+','\\\+')
323
324     assert_match /#{collection1_id}&#45;&gt;#{stage2_id}/, prov_svg
325     assert_match /#{collection1_id}&#45;&gt;#{stage3_id}/, prov_svg
326
327     assert_match /#{stage2_id}&#45;&gt;#{stage2_out}/, prov_svg
328     assert_match /#{stage3_id}&#45;&gt;#{stage3_out}/, prov_svg
329
330   end
331
332 end