marty 6.1.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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