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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +17 -3
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +3 -2
- data/Gemfile +2 -1
- data/app/assets/javascripts/marty/extjs/extensions/marty.js +23 -1
- data/app/assets/stylesheets/marty/application.css +4 -1
- data/app/assets/stylesheets/marty/dark_mode.css +17 -0
- data/app/components/marty/auth_app.rb +10 -1
- data/app/components/marty/auth_app/client/auth_app.js +4 -0
- data/app/components/marty/extras/layout.rb +2 -2
- data/app/components/marty/extras/misc.rb +1 -1
- data/app/components/marty/log_view.rb +0 -1
- data/app/components/marty/main_auth_app.rb +9 -12
- data/app/components/marty/promise_view.rb +5 -0
- data/app/components/marty/promise_view/client/promise_view.js +11 -0
- data/app/components/marty/schedule_jobs_dashboard.rb +30 -96
- data/app/components/marty/schedule_jobs_grid.rb +118 -0
- data/app/controllers/marty/application_controller.rb +6 -2
- data/app/models/marty/base.rb +48 -48
- data/app/models/marty/config.rb +14 -2
- data/app/models/marty/user.rb +12 -0
- data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +19 -0
- data/app/services/marty/enums/report.rb +18 -0
- data/app/services/marty/promises/delorean/create.rb +16 -27
- data/app/services/marty/promises/ruby/create.rb +10 -1
- data/app/views/layouts/marty/application.html.erb +4 -1
- data/app/views/marty/diagnostic/op.html.erb +3 -1
- data/config/locales/en.yml +2 -0
- data/db/migrate/512_add_promise_priority.rb +9 -0
- data/db/migrate/513_add_priority_to_promise_view.rb +44 -0
- data/db/migrate/514_remove_marty_events.rb +13 -0
- data/delorean/enum_report.dl +11 -0
- data/delorean/table_report.dl +7 -0
- data/docker-compose.dummy.yml +7 -4
- data/lib/marty.rb +5 -2
- data/lib/marty/api/base.rb +15 -9
- data/lib/marty/cache_adapters.rb +2 -0
- data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -5
- data/lib/marty/cache_adapters/memory_and_redis.rb +93 -0
- data/lib/marty/cache_adapters/redis.rb +63 -0
- data/lib/marty/delayed_job/scheduled_job_plugin.rb +33 -0
- data/lib/marty/diagnostic/database.rb +1 -2
- data/lib/marty/logger.rb +50 -17
- data/lib/marty/monkey.rb +26 -6
- data/lib/marty/promise_ruby_job.rb +2 -0
- data/lib/marty/rails_app.rb +29 -0
- data/lib/marty/railtie.rb +1 -0
- data/lib/marty/version.rb +1 -1
- data/marty.gemspec +1 -0
- data/spec/controllers/job_controller_spec.rb +2 -2
- data/spec/dummy/app/components/gemini/cm_auth_app.rb +12 -0
- data/spec/dummy/app/components/gemini/simple_view.rb +17 -0
- data/spec/dummy/app/jobs/test_failing_job.rb +14 -0
- data/spec/dummy/app/models/gemini/helper.rb +90 -1
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/db/migrate/20191101132729_add_activity_flag_to_simple.rb +10 -0
- data/spec/dummy/delorean/blame_report.dl +1 -0
- data/spec/dummy/delorean/enum_report.dl +1 -0
- data/spec/dummy/delorean/marty_fields.dl +1 -0
- data/spec/dummy/delorean/table_report.dl +1 -0
- data/spec/features/data_blame_report_spec.rb +66 -0
- data/spec/features/data_grid_spec.rb +1 -1
- data/spec/features/enum_values_report_spec.rb +76 -0
- data/spec/features/inline_editing_spec.rb +33 -0
- data/spec/features/rule_spec.rb +1 -1
- data/spec/features/schedule_jobs_dashboard_spec.rb +1 -1
- data/spec/features/scripting_spec.rb +1 -1
- data/spec/features/user_list_report_spec.rb +74 -0
- data/spec/fixtures/misc/struct_compare_tests.txt +15 -5
- data/spec/job_helper.rb +39 -0
- data/spec/jobs/cron_job_spec.rb +91 -0
- data/spec/lib/mcfly_model_spec.rb +9 -0
- data/spec/models/promise_spec.rb +168 -1
- data/spec/other/diagnostic/delayed_job_workers_spec.rb +1 -1
- data/spec/performance/caching_spec.rb +99 -0
- data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +34 -0
- data/spec/support/delayed_job_helpers.rb +3 -3
- data/spec/support/shared_connection.rb +9 -1
- data/spec/support/structure_compare.rb +19 -3
- metadata +39 -6
- data/app/components/marty/event_view.rb +0 -129
- data/app/models/marty/event.rb +0 -317
- data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +0 -8
- data/spec/dummy/delorean/blame_report.dl +0 -268
- data/spec/dummy/delorean/marty_fields.dl +0 -63
- data/spec/dummy/delorean/table_report.dl +0 -34
- 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
|
data/spec/features/rule_spec.rb
CHANGED
@@ -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('
|
25
|
+
let(:schedule_view) { netzke_find('schedule_jobs_grid') }
|
26
26
|
|
27
27
|
let!(:schedule) do
|
28
28
|
Marty::BackgroundJob::Schedule.create(
|
@@ -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)
|
data/spec/models/promise_spec.rb
CHANGED
@@ -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
|