marty 6.1.0 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitlab-ci.yml +17 -3
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +3 -2
  6. data/Gemfile +2 -1
  7. data/app/assets/javascripts/marty/extjs/extensions/marty.js +23 -1
  8. data/app/assets/stylesheets/marty/application.css +4 -1
  9. data/app/assets/stylesheets/marty/dark_mode.css +17 -0
  10. data/app/components/marty/auth_app.rb +10 -1
  11. data/app/components/marty/auth_app/client/auth_app.js +4 -0
  12. data/app/components/marty/extras/layout.rb +2 -2
  13. data/app/components/marty/extras/misc.rb +1 -1
  14. data/app/components/marty/log_view.rb +0 -1
  15. data/app/components/marty/main_auth_app.rb +9 -12
  16. data/app/components/marty/promise_view.rb +5 -0
  17. data/app/components/marty/promise_view/client/promise_view.js +11 -0
  18. data/app/components/marty/schedule_jobs_dashboard.rb +30 -96
  19. data/app/components/marty/schedule_jobs_grid.rb +118 -0
  20. data/app/controllers/marty/application_controller.rb +6 -2
  21. data/app/models/marty/base.rb +48 -48
  22. data/app/models/marty/config.rb +14 -2
  23. data/app/models/marty/user.rb +12 -0
  24. data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +19 -0
  25. data/app/services/marty/enums/report.rb +18 -0
  26. data/app/services/marty/promises/delorean/create.rb +16 -27
  27. data/app/services/marty/promises/ruby/create.rb +10 -1
  28. data/app/views/layouts/marty/application.html.erb +4 -1
  29. data/app/views/marty/diagnostic/op.html.erb +3 -1
  30. data/config/locales/en.yml +2 -0
  31. data/db/migrate/512_add_promise_priority.rb +9 -0
  32. data/db/migrate/513_add_priority_to_promise_view.rb +44 -0
  33. data/db/migrate/514_remove_marty_events.rb +13 -0
  34. data/delorean/enum_report.dl +11 -0
  35. data/delorean/table_report.dl +7 -0
  36. data/docker-compose.dummy.yml +7 -4
  37. data/lib/marty.rb +5 -2
  38. data/lib/marty/api/base.rb +15 -9
  39. data/lib/marty/cache_adapters.rb +2 -0
  40. data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -5
  41. data/lib/marty/cache_adapters/memory_and_redis.rb +93 -0
  42. data/lib/marty/cache_adapters/redis.rb +63 -0
  43. data/lib/marty/delayed_job/scheduled_job_plugin.rb +33 -0
  44. data/lib/marty/diagnostic/database.rb +1 -2
  45. data/lib/marty/logger.rb +50 -17
  46. data/lib/marty/monkey.rb +26 -6
  47. data/lib/marty/promise_ruby_job.rb +2 -0
  48. data/lib/marty/rails_app.rb +29 -0
  49. data/lib/marty/railtie.rb +1 -0
  50. data/lib/marty/version.rb +1 -1
  51. data/marty.gemspec +1 -0
  52. data/spec/controllers/job_controller_spec.rb +2 -2
  53. data/spec/dummy/app/components/gemini/cm_auth_app.rb +12 -0
  54. data/spec/dummy/app/components/gemini/simple_view.rb +17 -0
  55. data/spec/dummy/app/jobs/test_failing_job.rb +14 -0
  56. data/spec/dummy/app/models/gemini/helper.rb +90 -1
  57. data/spec/dummy/config/application.rb +1 -0
  58. data/spec/dummy/db/migrate/20191101132729_add_activity_flag_to_simple.rb +10 -0
  59. data/spec/dummy/delorean/blame_report.dl +1 -0
  60. data/spec/dummy/delorean/enum_report.dl +1 -0
  61. data/spec/dummy/delorean/marty_fields.dl +1 -0
  62. data/spec/dummy/delorean/table_report.dl +1 -0
  63. data/spec/features/data_blame_report_spec.rb +66 -0
  64. data/spec/features/data_grid_spec.rb +1 -1
  65. data/spec/features/enum_values_report_spec.rb +76 -0
  66. data/spec/features/inline_editing_spec.rb +33 -0
  67. data/spec/features/rule_spec.rb +1 -1
  68. data/spec/features/schedule_jobs_dashboard_spec.rb +1 -1
  69. data/spec/features/scripting_spec.rb +1 -1
  70. data/spec/features/user_list_report_spec.rb +74 -0
  71. data/spec/fixtures/misc/struct_compare_tests.txt +15 -5
  72. data/spec/job_helper.rb +39 -0
  73. data/spec/jobs/cron_job_spec.rb +91 -0
  74. data/spec/lib/mcfly_model_spec.rb +9 -0
  75. data/spec/models/promise_spec.rb +168 -1
  76. data/spec/other/diagnostic/delayed_job_workers_spec.rb +1 -1
  77. data/spec/performance/caching_spec.rb +99 -0
  78. data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +34 -0
  79. data/spec/support/delayed_job_helpers.rb +3 -3
  80. data/spec/support/shared_connection.rb +9 -1
  81. data/spec/support/structure_compare.rb +19 -3
  82. metadata +39 -6
  83. data/app/components/marty/event_view.rb +0 -129
  84. data/app/models/marty/event.rb +0 -317
  85. data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +0 -8
  86. data/spec/dummy/delorean/blame_report.dl +0 -268
  87. data/spec/dummy/delorean/marty_fields.dl +0 -63
  88. data/spec/dummy/delorean/table_report.dl +0 -34
  89. data/spec/models/event_spec.rb +0 -272
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'Inline editing', js: true do
4
+ before do
5
+ populate_test_users
6
+ end
7
+
8
+ it 'Adds a new record with default false value in checkbox' do
9
+ log_in_as('marty')
10
+ press('Pricing Config.')
11
+ press('Gemini Simple')
12
+ wait_for_ajax
13
+
14
+ grid = netzke_find('simple_view')
15
+ press 'Add'
16
+
17
+ row = grid.select_row(1)
18
+ user_id = row.all('.x-grid-cell')[0]
19
+ name = row.all('.x-grid-cell')[1]
20
+
21
+ user_id.double_click
22
+ user_id.fill_in 'user_id', with: 1
23
+
24
+ name.double_click
25
+ name.fill_in 'some_name', with: 'test name'
26
+
27
+ press 'Apply'
28
+ wait_for_ajax
29
+
30
+ model = Gemini::Simple.find_by(some_name: 'test name')
31
+ expect(model.active).to be false
32
+ end
33
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- feature 'rule view', js: true do
3
+ feature 'rule view', js: true, speed: :slow do
4
4
  before(:all) do
5
5
  marty_whodunnit
6
6
  @save_file = "/tmp/save_#{Process.pid}.psql"
@@ -22,7 +22,7 @@ feature 'Schedule Jobs Dashboard', js: true do
22
22
  end
23
23
 
24
24
  context 'as admin' do
25
- let(:schedule_view) { netzke_find('schedule_jobs_dashboard') }
25
+ let(:schedule_view) { netzke_find('schedule_jobs_grid') }
26
26
 
27
27
  let!(:schedule) do
28
28
  Marty::BackgroundJob::Schedule.create(
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- feature 'under Applications menu, Scripting workflows', js: true do
3
+ feature 'under Applications menu, Scripting workflows', js: true, speed: :slow do
4
4
  before(:all) do
5
5
  self.use_transactional_tests = false
6
6
 
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'User List report', js: true do
4
+ before(:each) do
5
+ populate_test_users
6
+ Marty::Script.load_scripts(nil)
7
+ end
8
+
9
+ def go_to_reporting
10
+ press('Applications')
11
+ press('Reports')
12
+ end
13
+
14
+ def with_user(uname)
15
+ u = Marty::User.find_by_login(uname)
16
+ begin
17
+ old_u, Mcfly.whodunnit = Mcfly.whodunnit, u
18
+ yield(u)
19
+ ensure
20
+ Mcfly.whodunnit = old_u
21
+ end
22
+ end
23
+
24
+ def select_node node_name
25
+ wait_for_ajax
26
+ # hacky: assumes only 1 combobox without label
27
+ within(:gridpanel, 'report_select', match: :first) do
28
+ # hacky, hardcoding netzkecombobox dropdown arrow name
29
+ arrow = find(:input, 'nodename')['data-componentid'] + '-trigger-picker'
30
+ find(:xpath, ".//div[@id='#{arrow}']").click
31
+ find(:xpath, "//li[text()='#{node_name}']").click
32
+ end
33
+ end
34
+
35
+ it 'run_script displays proper data' do
36
+ log_in_as('admin1')
37
+ go_to_reporting
38
+
39
+ tag_grid = netzke_find('tag_grid')
40
+ script_grid = netzke_find('script_grid')
41
+ posting_combo = netzke_find('Posting', 'combobox')
42
+ script_combo = netzke_find('', 'combobox')
43
+
44
+ by 'select tag' do
45
+ wait_for_ajax
46
+ tag_grid.select_row(1)
47
+ end
48
+
49
+ and_by 'select TableReport script & UserList node' do
50
+ find('table', text: 'TableReport').click
51
+ select_node('User List (csv)')
52
+ end
53
+
54
+ and_by 'generate report' do
55
+ wait_for_ajax
56
+ press('Generate Report')
57
+ wait_for_ajax
58
+ expect(page).to have_content('Generate: User List')
59
+ end
60
+
61
+ path = Rails.root.join('spec/tmp/downloads/User List.csv')
62
+ csv_content = CSV.parse File.read(path)
63
+
64
+ expect(csv_content.size > 1).to be true
65
+ expect(csv_content.first).to eq ['login', 'firstname', 'lastname', 'active', 'roles']
66
+
67
+ expected_row = [
68
+ 'marty', 'marty', 'marty', 'true',
69
+ 'admin, data_grid_editor, dev, user_manager, viewer'
70
+ ]
71
+ marty_row = csv_content.find { |row| row[0] == 'marty' }
72
+ expect(marty_row).to eq expected_row
73
+ end
74
+ end
@@ -57,12 +57,12 @@
57
57
  { "example_number": "11",
58
58
  "v1": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
59
59
  "v2": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["one"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
60
- "res": "path=[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] class mismatch Integer String"
60
+ "res": "path=[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] class mismatch Integer (1) != String (one)"
61
61
  },
62
62
  { "example_number": "12",
63
63
  "v1": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
64
64
  "v2": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
65
- "res": "path=[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] class mismatch Array Integer"
65
+ "res": "path=[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] class mismatch Array != Integer (1)"
66
66
  },
67
67
  { "example_number": "13",
68
68
  "v1": [{"a": 1, "b": 2, "c": 3, "d": 4},
@@ -118,12 +118,12 @@
118
118
  { "example_number": "21",
119
119
  "v1": [[[1,2,3], {"foo": "bar", "baz": null, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
120
120
  "v2": [[[1,2,3], {"foo": "bar", "baz": false, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
121
- "res": "path=[0][1][\"baz\"] class mismatch NilClass FalseClass"
121
+ "res": "path=[0][1][\"baz\"] class mismatch NilClass != FalseClass"
122
122
  },
123
123
  { "example_number": "22",
124
124
  "v1": [[[1,2,3], {"foo": "bar", "baz": true, "plugh": 2.434, "hash": { "value": [1,false] }}], 7, "value"],
125
125
  "v2": [[[1,2,3], {"foo": "bar", "baz": true, "plugh": 2.434, "hash": { "value": [1,null] }}], 7, "value"],
126
- "res": "path=[0][1][\"hash\"][\"value\"][1] class mismatch FalseClass NilClass"
126
+ "res": "path=[0][1][\"hash\"][\"value\"][1] class mismatch FalseClass != NilClass"
127
127
  },
128
128
  { "example_number": "23",
129
129
  "v1": {"pi": 3.1415926, "e": 2.7182818, "phi": 1.618034},
@@ -158,7 +158,7 @@
158
158
  { "example_number": "29",
159
159
  "v1": {"three": 3},
160
160
  "v2": {"three": 3.0},
161
- "res": "path=[\"three\"] class mismatch Integer Float",
161
+ "res": "path=[\"three\"] class mismatch Integer (3) != Float (3.0)",
162
162
  "cmp_opts": {"float_int_nomatch": true}
163
163
  },
164
164
  { "example_number": "30",
@@ -166,5 +166,15 @@
166
166
  "v2": {"three": 3.0, "dummy": "bye", "dummy2": 2},
167
167
  "res": false,
168
168
  "cmp_opts": {"float_int_nomatch": false, "ignore": ["dummy", "dummy2"]}
169
+ },
170
+ { "example_number": "31",
171
+ "v1": {"a": 1},
172
+ "v2": {"a": [1]},
173
+ "res": "path=[\"a\"] class mismatch Integer (1) != Array"
174
+ },
175
+ { "example_number": "32",
176
+ "v1": {"a": 1},
177
+ "v2": {"a": {"b": 1}},
178
+ "res": "path=[\"a\"] class mismatch Integer (1) != Hash"
169
179
  }
170
180
  ]
data/spec/job_helper.rb CHANGED
@@ -110,6 +110,43 @@ LOGGER:
110
110
  Gemini::Helper.testaction('message %d', msgid) && msgid
111
111
  EOS
112
112
 
113
+ NAME_M = 'PromiseM'
114
+ SCRIPT_M = <<EOS
115
+ Node:
116
+ secs =?
117
+ label =?
118
+ reverse =? false
119
+ job_cnt =?
120
+ call_sleep = Gemini::Helper.sleep(secs, label)
121
+ blocker_inds = Gemini::Helper.get_inds(8)
122
+ blocker_calls = [Node(secs=5,
123
+ p_title = "Blocker %d" % i,
124
+ label = "Blocker %d" % i) | "call_sleep"
125
+ for i in blocker_inds]
126
+ prio_inds = Gemini::Helper.get_inds(job_cnt)
127
+ prios = [(if reverse then job_cnt - i else i)
128
+ for i in prio_inds]
129
+ prio_both = prio_inds.zip(prios)
130
+ prio_calls = [
131
+ Node(secs=2,
132
+ p_title = "Prioritized %d pri=%d" % [i, prio],
133
+ label = "Prioritized %d pri=%d" % [i, prio],
134
+ p_priority = prio) | "call_sleep"
135
+ for i, prio in prio_both]
136
+ result = blocker_calls + prio_calls
137
+ EOS
138
+
139
+ NAME_N = 'PromiseN'
140
+ SCRIPT_N = <<EOS
141
+ Node:
142
+ title =?
143
+ child1 = Gemini::Helper.sleep(0, 'child1')
144
+ child2 = Gemini::Helper.sleep(0, 'child2')
145
+
146
+ result = [Node(p_title=title + ' child1') | "child1",
147
+ Node(p_title=title + ' child2', p_priority=10) | "child2"]
148
+ EOS
149
+
113
150
  def promise_bodies
114
151
  {
115
152
  NAME_A => SCRIPT_A,
@@ -123,5 +160,7 @@ def promise_bodies
123
160
  NAME_I => SCRIPT_I,
124
161
  NAME_J => SCRIPT_J,
125
162
  NAME_K => SCRIPT_K,
163
+ NAME_M => SCRIPT_M,
164
+ NAME_N => SCRIPT_N,
126
165
  }
127
166
  end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Cron jobs' do
4
+ let(:klass) { TestFailingJob }
5
+
6
+ let!(:schedule) do
7
+ Marty::BackgroundJob::Schedule.create!(
8
+ job_class: klass.name,
9
+ cron: '* * * * *',
10
+ state: 'on'
11
+ ).tap do |job|
12
+ Marty::BackgroundJob::UpdateSchedule.call(
13
+ id: job.id,
14
+ job_class: job.job_class
15
+ )
16
+
17
+ dj = klass.delayed_job
18
+ dj.update!(run_at: 1.minute.ago)
19
+ end
20
+ end
21
+
22
+ def run_job
23
+ expect(klass.delayed_job).to be_present
24
+ expect(Delayed::Job.count).to eq 1
25
+ worker = Delayed::Worker.new
26
+ worker.work_off
27
+ end
28
+
29
+ describe 'when delayed_job record is deleted during the execution' do
30
+ describe 'after failure' do
31
+ context 'schedule is on' do
32
+ before do
33
+ run_job
34
+ end
35
+
36
+ it 'job is recreated' do
37
+ expect(klass).to be_scheduled
38
+ expect(klass.delayed_job).to be_present
39
+ end
40
+ end
41
+
42
+ context 'schedule is off' do
43
+ before do
44
+ schedule.update!(state: :off)
45
+ run_job
46
+ end
47
+
48
+ it 'job is not recreated' do
49
+ expect(klass).to_not be_scheduled
50
+ expect(klass.delayed_job).to_not be_present
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'after success' do
56
+ before do
57
+ allow(klass).to receive(:trigger_failure).and_return(nil)
58
+ run_job
59
+ end
60
+
61
+ it 'job is not recreated' do
62
+ expect(klass).to_not be_scheduled
63
+ expect(klass.delayed_job).to_not be_present
64
+ end
65
+ end
66
+ end
67
+
68
+ describe 'logs' do
69
+ before do
70
+ allow(klass).to receive(:trigger_destroy).and_return(nil)
71
+ end
72
+
73
+ it 'logs failure' do
74
+ expect { run_job }.to change { Marty::BackgroundJob::Log.count }.by 1
75
+ log = Marty::BackgroundJob::Log.find_by(job_class: klass.name)
76
+ expect(log.error).to be_present
77
+ expect(log.failure?).to be true
78
+ expect(klass).to be_scheduled
79
+ end
80
+
81
+ it 'logs success' do
82
+ allow(klass).to receive(:trigger_failure).and_return(nil)
83
+
84
+ expect { run_job }.to change { Marty::BackgroundJob::Log.count }.by 1
85
+ log = Marty::BackgroundJob::Log.find_by(job_class: klass.name)
86
+ expect(log.error).to_not be_present
87
+ expect(log.success?).to be true
88
+ expect(klass).to be_scheduled
89
+ end
90
+ end
91
+ end
@@ -99,16 +99,25 @@ describe 'McflyModel' do
99
99
  Marty::Script.load_script_bodies({ 'E6' => (errscript3 % 'b_func_p') }, dt)
100
100
 
101
101
  @engine = Marty::ScriptSet.new.get_engine('AA')
102
+
103
+ mcfly_cache_adapter = ::Marty::CacheAdapters::McflyRubyCache.new(
104
+ size_per_class: 1000
105
+ )
106
+
107
+ ::Delorean::Cache.adapter = mcfly_cache_adapter
102
108
  end
109
+
103
110
  after(:all) do
104
111
  restore_clean_db(@clean_file)
105
112
  Marty::ScriptSet.clear_cache
106
113
  end
114
+
107
115
  let(:params) do
108
116
  { 'pt' => 'infinity',
109
117
  'entity' => Gemini::Entity.all.first,
110
118
  'note_rate' => 2.875 }
111
119
  end
120
+
112
121
  it 'lookup mode default' do
113
122
  a1 = @engine.evaluate('A', 'lookup', params)
114
123
  a2 = @engine.evaluate('A', 'clookup', params)
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'job_helper'
3
3
 
4
- describe Marty::Promise, slow: true do
4
+ describe Marty::Promise, slow: true, retry: 3 do
5
5
  before(:all) do
6
6
  @clean_file = "/tmp/clean_#{Process.pid}.psql"
7
7
  save_clean_db(@clean_file)
@@ -18,6 +18,7 @@ describe Marty::Promise, slow: true do
18
18
  end
19
19
 
20
20
  before(:each) do
21
+ ActiveRecord::Base.connection.reconnect!
21
22
  @time = DateTime.now
22
23
  expect(Marty::Promise.count).to eq(0)
23
24
  engine = Marty::ScriptSet.new.get_engine(NAME_A)
@@ -28,6 +29,7 @@ describe Marty::Promise, slow: true do
28
29
  end
29
30
 
30
31
  after(:each) do
32
+ ActiveRecord::Base.connection.reconnect!
31
33
  Marty::Log.delete_all
32
34
  Marty::Promise.where('parent_id IS NULL').destroy_all
33
35
  Timecop.return
@@ -208,4 +210,169 @@ describe Marty::Promise, slow: true do
208
210
  expect(promise.result['backtrace']).to_not be_empty
209
211
  end
210
212
  end
213
+
214
+ describe 'priority' do
215
+ let(:user) { Marty::User.find_by(login: 'marty') }
216
+
217
+ def run_prio_test(reverse: false, title:, runner:, job_cnt:)
218
+ real_title = title + ' ' + reverse.to_s
219
+ runner.call(reverse, real_title, job_cnt)
220
+ base_p = nil
221
+ timeout = 60
222
+ # wait until base promise completes
223
+ loop do
224
+ base_p = Marty::Promise.find_by(title: real_title)
225
+ break if p&.status || timeout == 0
226
+
227
+ timeout -= 1
228
+ sleep 1
229
+ end
230
+ [base_p, timeout]
231
+ end
232
+
233
+ it 'allows priority to be set' do
234
+ ruby_runner = lambda do |reverse, title, job_cnt|
235
+ Marty::Promises::Ruby::Create.call(
236
+ module_name: 'Gemini::Helper',
237
+ method_name: 'priority_tester',
238
+ method_args: [reverse, job_cnt],
239
+ params: { p_title: title, _user_id: user.id }
240
+ )
241
+ end
242
+
243
+ engine = Marty::ScriptSet.new.get_engine(NAME_M)
244
+ dl_runner = lambda do |reverse, title, job_cnt|
245
+ result = engine.background_eval('Node',
246
+ { 'p_title' => title,
247
+ 'job_cnt' => job_cnt,
248
+ 'reverse' => reverse }, ['result'])
249
+ end
250
+
251
+ results = []
252
+ job_cnt = 40
253
+ [['Ruby', ruby_runner],
254
+ ['Delorean', dl_runner]].each do |type, runner|
255
+ [true, false].each do |prio_order|
256
+ base_pr, timeout = run_prio_test(runner: runner,
257
+ title: 'Priority Base',
258
+ reverse: prio_order,
259
+ job_cnt: job_cnt)
260
+ expect(base_pr.is_a?(Marty::Promise)).to be_truthy
261
+ expect(base_pr.status).to be_truthy
262
+ expect(timeout).to be < 45
263
+ r = base_pr.result(true)['result']
264
+ expect(r.count).to eq(job_cnt + 8)
265
+ expect(r.to_set).to eq(Set.new(['5', '2']))
266
+
267
+ logs = Marty::Log.all.order(:id).attributes
268
+ Marty::Log.destroy_all
269
+ results << [type, prio_order, logs]
270
+ end
271
+ end
272
+
273
+ aggregate_failures do
274
+ results.each do |type, dirflag, logs|
275
+ err_str = "Type = #{type}, reverse=#{dirflag}"
276
+
277
+ # look at the log in order to see how the jobs ran
278
+ run_order = logs.map do |l|
279
+ label = l.dig('details', 'label')
280
+ label.starts_with?('Blocker') ? nil : label
281
+ end.compact
282
+
283
+ # get the priorities from the ordered list
284
+ pris = run_order.map do |r|
285
+ r.match(/pri=(\d+)/)[1].to_i
286
+ end
287
+ pc = pris.count
288
+
289
+ #####################################################################
290
+ # even though jobs start in order, variations in how they run (due to
291
+ # os scheduling) are still possible. Also, 'run off' jobs usually run
292
+ # out of order. Expect the log to be mostly in order (for any job,
293
+ # most of the jobs that ran before it should be lower and most after
294
+ # should be higher)
295
+ #####################################################################
296
+
297
+ # for each priority (job run) compute a score based on how many lower
298
+ # priority jobs preceeded it and higher priority jobs followed it.
299
+ comps = Array.new(pc) do |ind|
300
+ lhs = ind - 1
301
+ rhs = ind + 1
302
+ lha = lhs > -1 ? pris[0..lhs] : [] # jobs that ran before
303
+ rha = rhs <= pc - 1 ? pris[rhs..-1] : [] # jobs that ran after
304
+
305
+ # score is an array of t/f ; t indicates correct ordering
306
+ score = lha.map { |v| v < pris[ind] } + rha.map { |v| v > pris[ind] }
307
+ end
308
+
309
+ # count the trues for each job
310
+ scores = comps.map { |a| a.count { |v| v } }
311
+
312
+ avg_score = scores.sum.to_f / scores.count
313
+ target = job_cnt * 0.75
314
+
315
+ expect(avg_score).to be >= target
316
+ end
317
+ end
318
+ end
319
+
320
+ it 'inherits priority from parent' do
321
+ ruby_runner = lambda do |priority = nil|
322
+ title = "Ruby pri=#{priority}"
323
+ params = { p_title: title,
324
+ _user_id: user.id,
325
+ p_priority: priority }.compact
326
+ Marty::Promises::Ruby::Create.call(
327
+ module_name: 'Gemini::Helper',
328
+ method_name: 'priority_inh_tester',
329
+ method_args: [title],
330
+ params: params
331
+ )
332
+ title
333
+ end
334
+ engine = Marty::ScriptSet.new.get_engine(NAME_N)
335
+ dl_runner = lambda do |priority = nil|
336
+ title = "Delorean pri=#{priority}"
337
+ result = engine.background_eval('Node',
338
+ { 'p_title' => title,
339
+ 'title' => title,
340
+ 'p_priority' => priority }.compact,
341
+ ['result'])
342
+ title
343
+ end
344
+ results = []
345
+ [ruby_runner,
346
+ dl_runner].each do |runner|
347
+ [nil, 123].each do |priority|
348
+ title = runner.call(priority)
349
+ timeout = 10
350
+ r = nil
351
+ loop do
352
+ r = Marty::Promise.where("title like '#{title}%'").order(:title).
353
+ pluck(:title, :priority)
354
+ break if r.count == 3 || timeout == 0
355
+
356
+ timeout -= 1
357
+ sleep 1
358
+ end
359
+ results << r
360
+ end
361
+ end
362
+ # child1 should have same priority as parent, child2 should have 10
363
+ exp = [[['Ruby pri=', 0],
364
+ ['Ruby pri= child1', 0],
365
+ ['Ruby pri= child2', 10]],
366
+ [['Ruby pri=123', 123],
367
+ ['Ruby pri=123 child1', 123],
368
+ ['Ruby pri=123 child2', 10]],
369
+ [['Delorean pri=', 0],
370
+ ['Delorean pri= child1', 0],
371
+ ['Delorean pri= child2', 10]],
372
+ [['Delorean pri=123', 123],
373
+ ['Delorean pri=123 child1', 123],
374
+ ['Delorean pri=123 child2', 10]]]
375
+ expect(results).to eq(exp)
376
+ end
377
+ end
211
378
  end