marty 3.1.0 → 4.0.0.rc2

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -7
  3. data/.rubocop.yml +0 -1
  4. data/.rubocop_todo.yml +15 -2
  5. data/Dockerfile.dummy +0 -3
  6. data/Gemfile +15 -21
  7. data/Gemfile.lock +289 -0
  8. data/app/components/marty/data_grid_view.rb +18 -68
  9. data/app/components/marty/extras/layout.rb +1 -1
  10. data/app/components/marty/grid.rb +1 -1
  11. data/app/components/marty/import_view.rb +3 -3
  12. data/app/components/marty/main_auth_app.rb +1 -11
  13. data/app/components/marty/report_form.rb +6 -6
  14. data/app/components/marty/script_form.rb +5 -5
  15. data/app/components/marty/script_tester.rb +2 -2
  16. data/app/components/marty/user_view.rb +9 -3
  17. data/app/models/marty/data_grid.rb +11 -16
  18. data/app/models/marty/event.rb +2 -2
  19. data/app/models/marty/promise.rb +4 -4
  20. data/app/models/marty/role_type.rb +1 -14
  21. data/app/services/marty/data_grid_view/save_grid.rb +2 -2
  22. data/app/services/marty/promises/delorean/create.rb +2 -2
  23. data/app/services/marty/promises/ruby/create.rb +2 -2
  24. data/config/locales/en.yml +2 -11
  25. data/docker-compose.dummy.yml +0 -1
  26. data/lib/marty/content_handler.rb +2 -2
  27. data/lib/marty/data_change.rb +1 -1
  28. data/lib/marty/data_conversion.rb +4 -3
  29. data/lib/marty/data_importer.rb +4 -4
  30. data/lib/marty/mcfly_model.rb +10 -7
  31. data/lib/marty/migrations.rb +1 -1
  32. data/lib/marty/monkey.rb +2 -2
  33. data/lib/marty/promise_job.rb +5 -5
  34. data/lib/marty/promise_proxy.rb +2 -2
  35. data/lib/marty/promise_ruby_job.rb +4 -4
  36. data/lib/marty/version.rb +1 -1
  37. data/marty.gemspec +18 -13
  38. data/other/marty/diagnostic/aws/ec2_instance.rb +2 -17
  39. data/other/marty/diagnostic/database.rb +2 -2
  40. data/other/marty/diagnostic/delayed_job_version.rb +1 -0
  41. data/spec/dummy/app/models/gemini/fannie_bup.rb +20 -13
  42. data/spec/dummy/config/application.rb +0 -1
  43. data/spec/dummy/config/initializers/secret_token.rb +1 -1
  44. data/spec/features/data_grid_spec.rb +46 -109
  45. data/spec/features/reporting_spec.rb +4 -4
  46. data/spec/features/rule_spec.rb +1 -1
  47. data/spec/features/scripting_spec.rb +3 -3
  48. data/spec/features/user_view_spec.rb +8 -17
  49. data/spec/lib/data_importer_spec.rb +8 -8
  50. data/spec/lib/mcfly_model_spec.rb +6 -6
  51. data/spec/models/data_grid_spec.rb +4 -19
  52. data/spec/spec_helper.rb +2 -2
  53. data/spec/support/netzke.rb +3 -4
  54. metadata +53 -50
  55. data/.ssh-docker/.keep +0 -0
  56. data/app/components/marty/data_grid_user_view.rb +0 -39
  57. data/db/migrate/108_add_data_grid_perms.rb +0 -16
  58. data/other/marty/diagnostic/aws/error.rb +0 -8
@@ -41,7 +41,7 @@ module Marty::Migrations
41
41
  SQL
42
42
 
43
43
  db_values = res.first['enum_range'].gsub(/[{"}]/, '').split(',')
44
- ex_values = klass::VALUES.map(&:to_s) - db_values
44
+ ex_values = klass::VALUES - db_values
45
45
 
46
46
  return if ex_values.empty?
47
47
 
@@ -54,8 +54,8 @@ class Delorean::BaseModule::NodeCall
54
54
  begin
55
55
  # make sure params is serialzable before starting a Job
56
56
  JSON.dump(params)
57
- rescue StandardError => e
58
- raise "non-serializable parameters: #{params} #{e}"
57
+ rescue StandardError => exc
58
+ raise "non-serializable parameters: #{params} #{exc}"
59
59
  end
60
60
 
61
61
  Marty::Promises::Delorean::Create.call(
@@ -41,9 +41,9 @@ class Marty::PromiseJob < Struct.new(:promise,
41
41
  end
42
42
 
43
43
  # log "DONE #{Process.pid} #{promise.id} #{Time.now.to_f} #{res}"
44
- rescue StandardError => e
45
- res = Delorean::Engine.grok_runtime_exception(e)
46
- # log "ERR- #{Process.pid} #{promise.id} #{Time.now.to_f} #{e}"
44
+ rescue StandardError => exc
45
+ res = Delorean::Engine.grok_runtime_exception(exc)
46
+ # log "ERR- #{Process.pid} #{promise.id} #{Time.now.to_f} #{exc}"
47
47
  end
48
48
  promise.set_result(res)
49
49
  process_hook(res)
@@ -53,8 +53,8 @@ class Marty::PromiseJob < Struct.new(:promise,
53
53
  return unless hook
54
54
 
55
55
  hook.run(params: params, result: res)
56
- rescue StandardError => e
57
- Marty::Util.logger.error "promise hook failed: #{e}"
56
+ rescue StandardError => exc
57
+ Marty::Util.logger.error "promise hook failed: #{exc}"
58
58
  end
59
59
 
60
60
  def max_attempts
@@ -55,8 +55,8 @@ class Marty::PromiseProxy < BasicObject
55
55
  begin
56
56
  @result = @promise.wait_for_result(@timeout)
57
57
  @result = @result[@attr] if @attr && !@result['error']
58
- rescue ::Exception => e
59
- @result = ::Delorean::Engine.grok_runtime_exception(e)
58
+ rescue ::Exception => exc
59
+ @result = ::Delorean::Engine.grok_runtime_exception(exc)
60
60
  end
61
61
  end
62
62
  end
@@ -25,8 +25,8 @@ class Marty::PromiseRubyJob < Struct.new(:promise,
25
25
 
26
26
  mod = module_name.constantize
27
27
  res = { 'result' => mod.send(method_name, *method_args) }
28
- rescue StandardError => e
29
- res = ::Marty::Promise.exception_to_result(promise: promise, exception: e)
28
+ rescue StandardError => exc
29
+ res = ::Marty::Promise.exception_to_result(promise: promise, exception: exc)
30
30
  end
31
31
 
32
32
  promise.set_result(res)
@@ -37,8 +37,8 @@ class Marty::PromiseRubyJob < Struct.new(:promise,
37
37
  return unless hook
38
38
 
39
39
  hook.run(params: method_args, result: res)
40
- rescue StandardError => e
41
- Marty::Util.logger.error "promise hook failed: #{e}"
40
+ rescue StandardError => exc
41
+ Marty::Util.logger.error "promise hook failed: #{exc}"
42
42
  end
43
43
 
44
44
  def max_attempts
@@ -1,3 +1,3 @@
1
1
  module Marty
2
- VERSION = '3.1.0'
2
+ VERSION = '4.0.0.rc2'
3
3
  end
@@ -26,21 +26,26 @@ Gem::Specification.new do |s|
26
26
  'Marty is a framework for viewing and reporting on versioned data.'
27
27
  s.files = `git ls-files`.split($\)
28
28
  s.licenses = ['MIT']
29
- # used for signing aws ec2 requests
30
- s.add_dependency 'aws-sigv4'
31
- # Only pinning this because there's no other way around it for Axlsx.
32
- # DO NOT unpin this.
29
+
30
+ s.add_dependency 'pg', '~> 0.21'
31
+
32
+ s.add_dependency 'netzke', '6.5.0.0'
33
+
33
34
  s.add_dependency 'axlsx', '3.0.0pre'
35
+
36
+ s.add_dependency 'delorean_lang', '~> 1.0'
37
+ s.add_dependency 'mcfly', '~> 0.0.20'
38
+
34
39
  s.add_dependency 'coderay'
35
- s.add_dependency 'daemons'
36
- s.add_dependency 'delayed_cron_job'
37
- s.add_dependency 'delayed_job_active_record'
38
- s.add_dependency 'delorean_lang'
39
40
  s.add_dependency 'json-schema'
40
- s.add_dependency 'mcfly'
41
- s.add_dependency 'net-ldap'
42
- s.add_dependency 'netzke'
43
- s.add_dependency 'pg'
41
+ s.add_dependency 'net-ldap', '~> 0.16.1'
44
42
  s.add_dependency 'rubyzip'
45
- s.add_dependency 'zip-zip'
43
+ s.add_dependency 'sqlite3'
44
+
45
+ # used for signing aws ec2 requests
46
+ s.add_dependency 'aws-sigv4', '~> 1.0', '>= 1.0.2'
47
+
48
+ s.add_dependency 'daemons', '~> 1.3.1'
49
+ s.add_dependency 'delayed_cron_job'
50
+ s.add_dependency 'delayed_job_active_record'
46
51
  end
@@ -35,30 +35,15 @@ class Marty::Diagnostic::Aws::Ec2Instance < Marty::Aws::Request
35
35
 
36
36
  def ec2_request action, params = {}
37
37
  resp = request({ action: action }, params)
38
- parsed = Hash.from_xml(resp)
39
-
40
- # check AWS response for errors
41
- error = parsed.dig('Response', 'Errors', 'Error')
42
- raise Marty::Diagnostic::Aws::Error.new(action, error) if error
43
-
44
- action_resp = parsed["#{action}Response"]
45
- raise Marty::Diagnostic::Aws::Error.new(action, parsed) unless action_resp
46
-
47
- action_resp
38
+ Hash.from_xml(resp)["#{action}Response"]
48
39
  end
49
40
 
50
41
  def get_tag
51
- action = 'DescribeTags'
52
42
  params = { 'Filter.1.Name' => 'resource-id',
53
43
  'Filter.1.Value.1' => get_instance_id,
54
44
  'Filter.2.Name' => 'key',
55
45
  'Filter.2.Value.1' => 'Name' }
56
-
57
- action_resp = ec2_request(action, params)
58
- tag = action_resp.dig('tagSet', 'item', 'value')
59
- raise Marty::Diagnostic::Aws::Error.new(action, action_resp) unless tag
60
-
61
- tag
46
+ ec2_request('DescribeTags', params)['tagSet']['item']['value']
62
47
  end
63
48
 
64
49
  def get_instances
@@ -20,9 +20,9 @@ module Marty::Diagnostic::Database
20
20
  end
21
21
 
22
22
  def self.db_schema
23
- current = ActiveRecord::Migration.current_version
23
+ current = ActiveRecord::Migrator.current_version
24
24
  raise "Migration is needed.\nCurrent Version: #{current}" if
25
- ActiveRecord::Base.connection.migration_context.needs_migration?
25
+ ActiveRecord::Migrator.needs_migration?
26
26
 
27
27
  current.to_s
28
28
  end
@@ -31,6 +31,7 @@ module Marty::Diagnostic; class DelayedJobVersion < Base
31
31
  hash[r[0]] ||= []
32
32
  hash[r[0]] << r[1]
33
33
  end.map do |node, result|
34
+
34
35
  versions = result.uniq
35
36
  status = versions.count == 1 && versions[0] == ENV['DELAYED_VER']
36
37
 
@@ -29,63 +29,70 @@ module Gemini
29
29
  gen_mcfly_lookup :lookup, {
30
30
  entity: true,
31
31
  note_rate: false
32
- }
32
+ }, to_hash: true
33
+
33
34
  gen_mcfly_lookup :lookup_p, {
34
35
  entity: true,
35
36
  note_rate: false
36
- }, private: true
37
+ }, to_hash: false
38
+
37
39
  gen_mcfly_lookup :clookup, {
38
40
  entity: true,
39
41
  note_rate: false
40
- }, cache: true
42
+ }, cache: true, to_hash: true
43
+
41
44
  gen_mcfly_lookup :clookup_p, {
42
45
  entity: true,
43
46
  note_rate: false
44
- }, cache: true, private: true
47
+ }, cache: true, to_hash: false
48
+
45
49
  gen_mcfly_lookup :lookupn, {
46
50
  entity: true,
47
51
  note_rate: false
48
- }, mode: nil
52
+ }, mode: nil, to_hash: true
53
+
49
54
  gen_mcfly_lookup :lookupn_p, {
50
55
  entity: true,
51
56
  note_rate: false
52
- }, private: true, mode: nil
57
+ }, to_hash: false, mode: nil
58
+
53
59
  gen_mcfly_lookup :clookupn, {
54
60
  entity: true,
55
61
  note_rate: false
56
- }, cache: true, mode: nil
62
+ }, cache: true, mode: nil, to_hash: true
63
+
57
64
  gen_mcfly_lookup :clookupn_p, {
58
65
  entity: true,
59
66
  note_rate: false
60
- }, cache: true, private: true, mode: nil
67
+ }, cache: true, to_hash: false, mode: nil
61
68
 
62
- mcfly_lookup :a_func, sig: 3 do
69
+ mcfly_lookup :a_func, sig: 3, to_hash: true do
63
70
  |pt, e_id, bc_id|
64
71
  where(entity_id: e_id, bud_category_id: bc_id).
65
72
  order(:settlement_mm)
66
73
  end
67
74
 
68
- mcfly_lookup :b_func, sig: [3, 4] do
75
+ mcfly_lookup :b_func, sig: [3, 4], to_hash: true do
69
76
  |pt, e_id, bc_id, mm = nil|
70
77
  q = where(entity_id: e_id, bud_category_id: bc_id)
71
78
  q = q.where(settlement_mm: mm) if mm
72
79
  q.order(:settlement_mm).first
73
80
  end
74
81
 
75
- mcfly_lookup :a_func_p, sig: 3, private: true do
82
+ mcfly_lookup :a_func_p, sig: 3, to_hash: false do
76
83
  |pt, e_id, bc_id|
77
84
  where(entity_id: e_id, bud_category_id: bc_id).
78
85
  order(:settlement_mm)
79
86
  end
80
87
 
81
- mcfly_lookup :b_func_p, sig: [3, 4], private: true do
88
+ mcfly_lookup :b_func_p, sig: [3, 4], to_hash: false do
82
89
  |pt, e_id, bc_id, mm = nil|
83
90
  q = where(entity_id: e_id, bud_category_id: bc_id)
84
91
  q = q.where(settlement_mm: mm) if mm
85
92
  q.order(:settlement_mm)
86
93
  end
87
94
 
88
- cached_mcfly_lookup :ca_func, sig: 3 do
95
+ cached_mcfly_lookup :ca_func, sig: 3, to_hash: true do
89
96
  |pt, e_id, bc_id|
90
97
  where(entity_id: e_id, bud_category_id: bc_id).
91
98
  order(:settlement_mm)
@@ -79,7 +79,6 @@ module Dummy
79
79
  :dev,
80
80
  :viewer,
81
81
  :user_manager,
82
- :data_grid_editor,
83
82
  ]
84
83
  #config.marty.default_posting_type = 'BASE'
85
84
  config.secret_key_base = "SECRET_KEY_BASE"
@@ -4,4 +4,4 @@
4
4
  # If you change this key, all old signed cookies will become invalid!
5
5
  # Make sure the secret is at least 30 characters and all random,
6
6
  # no regular words or you'll be exposed to dictionary attacks.
7
- Dummy::Application.config.secret_key_base = 'a087c113817a728e1552d63682abbd7f19fd7481ea3e24154889af53ab56d114f8901f63909a5e8310260c7187f6fd203cddc8743f6486fead0a4043cd7976c2'
7
+ Dummy::Application.config.secret_token = 'a087c113817a728e1552d63682abbd7f19fd7481ea3e24154889af53ab56d114f8901f63909a5e8310260c7187f6fd203cddc8743f6486fead0a4043cd7976c2'
@@ -2,10 +2,6 @@ require 'spec_helper'
2
2
  require 'marty_rspec'
3
3
 
4
4
  feature 'data grid view', js: true do
5
- before(:all) do
6
- self.use_transactional_tests = true
7
- end
8
-
9
5
  before(:each) do
10
6
  marty_whodunnit
11
7
  Marty::Script.load_scripts
@@ -15,22 +11,12 @@ feature 'data grid view', js: true do
15
11
  n = File.basename(path, '.txt').camelize
16
12
  Marty::DataGrid.create_from_import(n, File.read(path), dt)
17
13
  end
18
- u = Marty::User.create!(login: 'grid_user',
19
- firstname: 'grid',
20
- lastname: 'user',
21
- active: true)
22
- Marty::UserRole.create!(user_id: u.id, role: 'data_grid_editor')
23
- @no_perm = { 'view' => [],
24
- 'edit_data' => [],
25
- 'edit_all' => [] }
26
14
  end
27
15
 
28
- def go_to_data_grids(admin: true)
29
- log_in_as(admin ? 'marty' : 'grid_user')
16
+ def go_to_data_grids
30
17
  press('Applications')
31
- dest = 'Data Grids' + (admin ? ' Admin' : '')
32
- press(dest)
33
- expect(page).to have_content dest
18
+ press('Data Grids')
19
+ expect(page).to have_content 'Data Grids'
34
20
  end
35
21
 
36
22
  # setup the info for the ext grid
@@ -38,7 +24,7 @@ feature 'data grid view', js: true do
38
24
  widths, rows = get_grid_info
39
25
  @grid = page.all(:xpath,
40
26
  ".//div[contains(@class, 'x-grid-item-container')]").
41
- reject { |e| e.text.include?('DataGrid1') }.first
27
+ reject { |e| e.text.include?('2016-12-31') }.first
42
28
  @gridx = @grid.native.rect.x
43
29
  @gridy = @grid.native.rect.y
44
30
  height = @grid.native.size.height
@@ -292,96 +278,18 @@ feature 'data grid view', js: true do
292
278
  expect(grid_meta).to eq(exp_meta)
293
279
  end
294
280
 
295
- it 'dg perms' do
296
- log_in_as('marty')
297
- go_to_data_grids
298
- dgv = netzke_find('data_grid_view')
299
- grids = dgv.get_col_vals('name', 5)
300
- set_one = lambda do |grid, perms|
301
- pos = grids.index(grid) + 1
302
- dgv.select_row(pos)
303
- press('Edit')
304
- wait_for_ajax
305
- view_combo = netzke_find('Can View', 'combobox')
306
- edit_data_combo = netzke_find('Can Edit Data', 'combobox')
307
- edit_all_combo = netzke_find('Can Edit All', 'combobox')
308
- view_combo.select_values(perms['view'].join(', '))
309
- edit_data_combo.select_values(perms['edit_data'].join(', '))
310
- edit_all_combo.select_values(perms['edit_all'].join(', '))
311
- press('OK')
312
- wait_for_ajax
313
- end
314
- dg1p = { 'view' => ['Data Grid Editor'],
315
- 'edit_data' => ['Data Grid Editor'],
316
- 'edit_all' => ['Admin'] }
317
- dg2p = { 'view' => ['Data Grid Editor', 'User Manager'],
318
- 'edit_data' => ['Data Grid Editor', 'User Manager'],
319
- 'edit_all' => ['Data Grid Editor', 'Admin'] }
320
- set_one.call('DataGrid1', dg1p)
321
- set_one.call('DataGrid2', dg2p)
322
- dg1 = Marty::DataGrid.mcfly_pt('infinity').find_by(name: 'DataGrid1')
323
- dg2 = Marty::DataGrid.mcfly_pt('infinity').find_by(name: 'DataGrid2')
324
- fixexp = lambda do |orig_h|
325
- orig_h.each_with_object({}) do |(k, v), h|
326
- h[k] = v.map do |vals|
327
- vals.split(' ').map(&:downcase).join('_')
328
- end.sort
329
- end
330
- end
331
- exp1 = fixexp.call(dg1p)
332
- exp2 = fixexp.call(dg2p)
333
- fixgot = ->(orig_h) { Hash[orig_h.map { |k, v| [k, v.sort] }] }
334
- got1 = fixgot.call(dg1.permissions)
335
- got2 = fixgot.call(dg2.permissions)
336
- cmp1 = struct_compare(got1, exp1)
337
- cmp2 = struct_compare(got2, exp2)
338
- expect(cmp1).to be_falsey
339
- expect(cmp2).to be_falsey
340
-
341
- set_one.call('DataGrid1', @no_perm)
342
- set_one.call('DataGrid2', @no_perm)
343
- dg1 = Marty::DataGrid.mcfly_pt('infinity').find_by(name: 'DataGrid1')
344
- dg2 = Marty::DataGrid.mcfly_pt('infinity').find_by(name: 'DataGrid2')
345
- expect(dg1.permissions).to eq(@no_perm)
346
- expect(dg2.permissions).to eq(@no_perm)
347
- end
348
-
349
- it 'show grid' do
281
+ it 'dg editor' do
350
282
  log_in_as('marty')
351
283
  go_to_data_grids
352
284
  dgv = netzke_find('data_grid_view')
353
285
  grids = dgv.get_col_vals('name', 5)
354
- pos = grids.index('DataGrid1') + 1
355
- dgv.select_row(pos)
356
- press('Show Grid')
357
- expect(page).to have_content('Antwerp')
358
- end
359
-
360
- def set_perm(perm)
361
- p = @no_perm + (perm ? { perm => ['data_grid_editor'] } : {})
362
- Marty::DataGrid.mcfly_pt('infinity').each do |dg|
363
- dg.permissions = p
364
- dg.save!
365
- end
366
- end
367
-
368
- it 'dg context menus' do
369
- log_in_as('grid_user')
370
- go_to_data_grids(admin: false)
371
- dgv = netzke_find('data_grid_user_view')
372
- context_test_all = ENV['DG_FEATURE_FULL'] == 'true'
373
- [[nil, nil],
374
- ['edit_all', context_test_all],
286
+ context_test_all = ENV['DG_FEATURE_QUICK'] != 'true'
287
+ [['edit_all', context_test_all],
375
288
  ['edit_data', false],
376
289
  ['view', false]].each do |perm, all_cells|
377
- set_perm(perm)
378
- press('Refresh')
379
- grids = dgv.get_col_vals('name', 5)
380
- if perm.nil?
381
- expect(grids).to be_nil
382
- next
383
- end
384
290
  grids.each do |grid|
291
+ Marty::Config['grid_edit_edit_perm'] = perm
292
+ Marty::Config['grid_edit_save_perm'] = perm
385
293
  pos = grids.index(grid) + 1
386
294
  dgv.select_row(pos)
387
295
  press('Edit Grid')
@@ -392,14 +300,8 @@ feature 'data grid view', js: true do
392
300
  wait_for_ajax
393
301
  end
394
302
  end
395
- end
396
-
397
- it 'dg editor' do
398
- set_perm('edit_all')
399
- log_in_as('grid_user')
400
- go_to_data_grids(admin: false)
401
- dgv = netzke_find('data_grid_user_view')
402
- grids = dgv.get_col_vals('name', 5)
303
+ Marty::Config['grid_edit_edit_perm'] = 'edit_all'
304
+ Marty::Config['grid_edit_save_perm'] = 'edit_all'
403
305
 
404
306
  # now test some editing, saving, and cancel logic
405
307
  get_latest = lambda do
@@ -558,5 +460,40 @@ feature 'data grid view', js: true do
558
460
  press('Save')
559
461
  sleep 1
560
462
  validate_grid('1')
463
+
464
+ Marty::Config['grid_edit_save_perm'] = 'view'
465
+ pos = grids.index('DataGrid5') + 1
466
+ dgv.select_row(pos)
467
+ count = 1
468
+ begin
469
+ press('Edit Grid')
470
+ wait_for_ajax
471
+ grid_setup
472
+ cell_edit(2, 3, '123.456')
473
+ rescue StandardError => e
474
+ if count > 0
475
+ sleep 1
476
+ retry
477
+ end
478
+ count -= 1
479
+ end
480
+ press('Save')
481
+ errexp = 'error: save_grid: entered with view permissions'
482
+ expect(page).to have_content(errexp)
483
+ press('OK')
484
+ wait_for_ajax
485
+ context_click(2, 4, 4, click: true)
486
+ Marty::Config['grid_edit_save_perm'] = 'edit_data'
487
+ press('Save')
488
+ errexp = 'error: save_grid: grid modification not allowed'
489
+ expect(page).to have_content(errexp)
490
+ exp_log = JSON.parse(File.read('spec/fixtures/misc/grid_log_errs.json'))
491
+ got = Marty::Log.all.select(:message, :details).attributes.map do |l|
492
+ newl = l.except('id')
493
+ newl['details'].delete('rec_id')
494
+ newl
495
+ end
496
+ cmp = struct_compare(got, exp_log)
497
+ expect(cmp).to be_falsey
561
498
  end
562
499
  end