joblin 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e87b736e32f7b502592a72a08da939d0a2ef95302e96b491f8d258bcdafcfab1
4
- data.tar.gz: 97df4d3166bb653f6362d11b600c91a805c4cdc368091690265ae2c686c136d2
3
+ metadata.gz: 2e541ee5d08b606fc0acf25a8e2aae3fb45a24d430befdfaadefd01b12e3c05c
4
+ data.tar.gz: 901c664b71c3f615d337f0c9a41f52d95153af1234632a4b9dd4d8d527c87afb
5
5
  SHA512:
6
- metadata.gz: 352592cc3733c55879d51ac118c242184140d174f7873b632a6695a7536a641493a3515377a5b838bc1d27e22c9cb5092517b8d4096098b96cfc124561ea558e
7
- data.tar.gz: a818e8053990c09106eb47a21ca8bd70aeea51a21a3d21f8a14de54bd6f03e7aa4931913d145b90db8a8c3907eb1bb78ae0dd4f25cc3e07450d120bead87cde8
6
+ metadata.gz: 9baf25436897e081d908f8f77873b9409b7b1fde6c47925a4b4309875ba9bbc1445ed3ec8ec2f3591a395c90c47f98854d84b5cf5ee2ce01ce519dd9aa16c43c
7
+ data.tar.gz: 8f0b5f1a644913410633f52227d26c2eabfb1770aaf3452b511be8409f0245c605bfa803093bef8783811e2b86d710d5b1fa3e097d7a1d323e94efd121050cc9
@@ -107,7 +107,7 @@ module Joblin::Batching
107
107
  private
108
108
 
109
109
  def get_parent_hash(bid)
110
- resolve_hash(get_parent_bid(bid)).freeze
110
+ resolve_hash(get_parent_bid(bid))
111
111
  end
112
112
 
113
113
  def get_parent_bid(bid)
@@ -153,11 +153,27 @@ module Joblin::Batching
153
153
  end
154
154
 
155
155
  def load_all
156
- resolve_hash(@bid_stack[0]).freeze
156
+ resolve_hash(@bid_stack[0])
157
157
  while @bid_stack[0].present?
158
158
  get_parent_hash(@bid_stack[0])
159
159
  end
160
+ freeze_inherited!
160
161
  @hash_map
161
162
  end
163
+
164
+ # Every hash but `local` is inherited: #[]=, #delete and #save! only ever touch `local`, so a
165
+ # write to any other hash would be silently dropped rather than persisted. Freeze those to
166
+ # surface the mistake.
167
+ #
168
+ # `local` itself is deliberately left as-is - `resolve_hash` has already frozen it unless the
169
+ # owning Batch set allow_context_changes, and re-freezing it here would break writes on an
170
+ # editable Batch as a side effect of merely _reading_ the context.
171
+ def freeze_inherited!
172
+ writable_bid = local_bid
173
+ @hash_map.each do |bid, hash|
174
+ next if hash.nil? || bid == writable_bid
175
+ hash.freeze
176
+ end
177
+ end
162
178
  end
163
179
  end
@@ -1,3 +1,3 @@
1
1
  module Joblin
2
- VERSION = "0.2.0".freeze
2
+ VERSION = "0.2.1".freeze
3
3
  end
@@ -40,6 +40,92 @@ RSpec.describe Joblin::Batching::ContextHash do
40
40
  Sidekiq::Worker.drain_all
41
41
  end
42
42
 
43
+ describe 'writability of a resolved context' do
44
+ # A Batch that both reads and writes its own context - the ReportSyncTask pattern. Reading the
45
+ # context as a Hash (present?, blank?, each, to_h, ... all route through #flatten) used to
46
+ # freeze the underlying hash and defeat allow_context_changes for the rest of the job.
47
+ def editable_batch(context)
48
+ Joblin::Batching::Batch.new.tap do |b|
49
+ b.allow_context_changes = true
50
+ b.context = context
51
+ b.jobs { }
52
+ end
53
+ end
54
+
55
+ it 'stays writable after the context is read as a Hash' do
56
+ batch = editable_batch(report_class: 'SomeReport')
57
+ ctx = Joblin::Batching::Batch.new(batch.bid).context
58
+
59
+ expect(ctx.present?).to eq(true)
60
+ expect { ctx[:report_attempt] = 1 }.not_to raise_error
61
+ expect(ctx[:report_attempt]).to eq(1)
62
+ end
63
+
64
+ it 'persists writes made after the context was read' do
65
+ batch = editable_batch(report_class: 'SomeReport')
66
+ loaded = Joblin::Batching::Batch.new(batch.bid)
67
+ loaded.context.to_h
68
+ loaded.context[:report_id] = 'some_report/1'
69
+ loaded.save_context_changes
70
+
71
+ expect(Joblin::Batching::Batch.new(batch.bid).context[:report_id]).to eq('some_report/1')
72
+ end
73
+
74
+ it 'leaves a non-editable context frozen' do
75
+ batch = Joblin::Batching::Batch.new
76
+ batch.context = { report_class: 'SomeReport' }
77
+ batch.jobs { }
78
+ ctx = Joblin::Batching::Batch.new(batch.bid).context
79
+ ctx.present?
80
+
81
+ expect { ctx[:report_attempt] = 1 }.to raise_error(FrozenError)
82
+ end
83
+
84
+ # `local` is "the nearest ancestor that actually has a context", so for a context-less Batch
85
+ # the writable hash belongs to an ancestor - #save! writes it back to that ancestor's key.
86
+ it 'stays writable when the context lives on an ancestor Batch' do
87
+ parent = editable_batch(sync_batch_id: 7)
88
+ child_bid = nil
89
+ parent.jobs do
90
+ child = Joblin::Batching::Batch.new
91
+ child.jobs { }
92
+ child_bid = child.bid
93
+ end
94
+
95
+ ctx = Joblin::Batching::Batch.new(child_bid).context
96
+ ctx.present?
97
+
98
+ expect(ctx.local_bid).to eq(parent.bid)
99
+ expect { ctx[:report_attempt] = 1 }.not_to raise_error
100
+ expect(ctx[:sync_batch_id]).to eq(7)
101
+ end
102
+
103
+ it 'freezes inherited contexts that writes would silently drop' do
104
+ grandparent = editable_batch(level: 'grandparent')
105
+ parent_bid = nil
106
+ child_bid = nil
107
+ grandparent.jobs do
108
+ parent = Joblin::Batching::Batch.new
109
+ parent.allow_context_changes = true
110
+ parent.context = { level: 'parent' }
111
+ parent.jobs do
112
+ child = Joblin::Batching::Batch.new
113
+ child.jobs { }
114
+ child_bid = child.bid
115
+ end
116
+ parent_bid = parent.bid
117
+ end
118
+
119
+ ctx = Joblin::Batching::Batch.new(child_bid).context
120
+ ctx.to_h
121
+ hash_map = ctx.send(:instance_variable_get, :@hash_map)
122
+
123
+ expect(ctx.local_bid).to eq(parent_bid)
124
+ expect(hash_map[parent_bid]).not_to be_frozen
125
+ expect(hash_map[grandparent.bid]).to be_frozen
126
+ end
127
+ end
128
+
43
129
  it 'works with a callback batch' do
44
130
  ActiveJob::Base.queue_adapter = :sidekiq
45
131
  b = Joblin::Batching::Batch.new