tataru 0.1.0 → 0.2.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/Gemfile +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/tataru.rb +70 -12
- data/lib/tataru/base_resource.rb +49 -0
- data/lib/tataru/base_resource_desc.rb +35 -0
- data/lib/tataru/compiler.rb +100 -0
- data/lib/tataru/create_subroutines.rb +31 -0
- data/lib/tataru/delete_subroutines.rb +42 -0
- data/lib/tataru/flattener.rb +81 -0
- data/lib/tataru/init_hash_compiler.rb +41 -0
- data/lib/tataru/instruction.rb +52 -7
- data/lib/tataru/instruction_hash.rb +54 -0
- data/lib/tataru/instructions/call_instruction.rb +19 -0
- data/lib/tataru/instructions/check_create_instruction.rb +27 -0
- data/lib/tataru/instructions/check_delete_instruction.rb +20 -0
- data/lib/tataru/instructions/check_instruction.rb +26 -0
- data/lib/tataru/instructions/check_update_instruction.rb +27 -0
- data/lib/tataru/instructions/clear_instruction.rb +12 -0
- data/lib/tataru/instructions/compare_instruction.rb +16 -0
- data/lib/tataru/instructions/create_instruction.rb +20 -0
- data/lib/tataru/instructions/delete_instruction.rb +14 -0
- data/lib/tataru/instructions/end_instruction.rb +12 -0
- data/lib/tataru/instructions/goto_if_instruction.rb +26 -0
- data/lib/tataru/instructions/immediate_mode_instruction.rb +12 -0
- data/lib/tataru/instructions/init_instruction.rb +27 -0
- data/lib/tataru/instructions/key_instruction.rb +12 -0
- data/lib/tataru/instructions/mark_deletable_instruction.rb +13 -0
- data/lib/tataru/instructions/read_instruction.rb +28 -0
- data/lib/tataru/instructions/rescmp_instruction.rb +34 -0
- data/lib/tataru/instructions/resource_instruction.rb +15 -0
- data/lib/tataru/instructions/return_instruction.rb +16 -0
- data/lib/tataru/instructions/update_instruction.rb +16 -0
- data/lib/tataru/instructions/value_instruction.rb +15 -0
- data/lib/tataru/instructions/value_rom_instruction.rb +23 -0
- data/lib/tataru/instructions/value_update_instruction.rb +18 -0
- data/lib/tataru/memory.rb +16 -0
- data/lib/tataru/quest.rb +43 -0
- data/lib/tataru/representation.rb +22 -0
- data/lib/tataru/representations/array_representation.rb +18 -0
- data/lib/tataru/representations/hash_representation.rb +20 -0
- data/lib/tataru/representations/literal_representation.rb +9 -0
- data/lib/tataru/representations/output_representation.rb +19 -0
- data/lib/tataru/representations/resource_representation.rb +49 -0
- data/lib/tataru/resolver.rb +39 -0
- data/lib/tataru/resource_dsl.rb +21 -71
- data/lib/tataru/resource_type_pool.rb +22 -0
- data/lib/tataru/rom_reader.rb +47 -0
- data/lib/tataru/runner.rb +48 -0
- data/lib/tataru/sub_planner.rb +41 -0
- data/lib/tataru/subroutine_compiler.rb +52 -0
- data/lib/tataru/top_dsl.rb +35 -0
- data/lib/tataru/update_subroutines.rb +111 -0
- data/lib/tataru/version.rb +1 -1
- data/spec/compiler_spec.rb +181 -0
- data/spec/flattener_spec.rb +88 -0
- data/spec/init_hash_compiler_spec.rb +85 -0
- data/spec/instruction_hash_spec.rb +63 -0
- data/spec/instruction_spec.rb +36 -0
- data/spec/instructions/call_instruction_spec.rb +28 -0
- data/spec/instructions/check_create_instruction_spec.rb +67 -0
- data/spec/instructions/check_delete_instruction_spec.rb +47 -0
- data/spec/instructions/check_update_instruction_spec.rb +67 -0
- data/spec/instructions/clear_instruction_spec.rb +16 -0
- data/spec/instructions/compare_instruction_spec.rb +29 -0
- data/spec/instructions/create_instruction_spec.rb +62 -0
- data/spec/instructions/delete_instruction_spec.rb +20 -0
- data/spec/instructions/end_instruction_spec.rb +15 -0
- data/spec/instructions/goto_if_instruction_spec.rb +42 -0
- data/spec/instructions/init_instruction_spec.rb +16 -0
- data/spec/instructions/key_instruction_spec.rb +15 -0
- data/spec/instructions/mark_deletable_instruction_spec.rb +20 -0
- data/spec/instructions/read_instruction_spec.rb +34 -0
- data/spec/instructions/rescmp_instruction_spec.rb +113 -0
- data/spec/instructions/return_instruction_spec.rb +28 -0
- data/spec/instructions/update_instruction_spec.rb +39 -0
- data/spec/instructions/value_instruction_spec.rb +27 -0
- data/spec/instructions/value_rom_instruction_spec.rb +170 -0
- data/spec/instructions/value_update_instruction_spec.rb +35 -0
- data/spec/quest_spec.rb +9 -0
- data/spec/representations/array_representation_spec.rb +29 -0
- data/spec/representations/hash_representation_spec.rb +29 -0
- data/spec/representations/literal_representation_spec.rb +10 -0
- data/spec/representations/output_representation_spec.rb +11 -0
- data/spec/representations/resource_representation_spec.rb +50 -0
- data/spec/resource_dsl_spec.rb +71 -0
- data/spec/runner_spec.rb +99 -0
- data/spec/spec_helper.rb +401 -0
- data/spec/subroutine_compiler_spec.rb +39 -0
- data/spec/taru_spec.rb +616 -0
- data/spec/top_dsl_spec.rb +68 -0
- data/tataru.gemspec +45 -0
- metadata +145 -27
- data/Rakefile +0 -8
- data/lib/tataru/default_resource_finder.rb +0 -10
- data/lib/tataru/execution_step.rb +0 -79
- data/lib/tataru/planner.rb +0 -93
- data/lib/tataru/requirements.rb +0 -67
- data/lib/tataru/requirements_dsl.rb +0 -33
- data/lib/tataru/resource.rb +0 -52
- data/lib/tataru/state.rb +0 -64
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tataru'
|
4
|
+
|
5
|
+
describe Tataru::Runner do
|
6
|
+
it 'can be made' do
|
7
|
+
runner = Tataru::Runner.new([])
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns ended when ended' do
|
11
|
+
runner = Tataru::Runner.new([])
|
12
|
+
expect(runner).to be_ended
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns not ended when not ended' do
|
16
|
+
runner = Tataru::Runner.new([Tataru::Instruction.new])
|
17
|
+
expect(runner).to_not be_ended
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns ended when ended' do
|
21
|
+
runner = Tataru::Runner.new([Tataru::Instruction.new])
|
22
|
+
runner.memory.end = true
|
23
|
+
expect(runner).to be_ended
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns ended when errored' do
|
27
|
+
runner = Tataru::Runner.new([Tataru::Instruction.new])
|
28
|
+
runner.memory.error = 'Something'
|
29
|
+
expect(runner).to be_ended
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'sets memory error if instruction throws' do
|
33
|
+
inst1 = Tataru::Instruction.new
|
34
|
+
allow(inst1).to receive(:run) { raise 'hello' }
|
35
|
+
|
36
|
+
runner = Tataru::Runner.new([inst1])
|
37
|
+
runner.run_next
|
38
|
+
|
39
|
+
expect(runner).to be_ended
|
40
|
+
expect(runner.memory.error).to be_a RuntimeError
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'runs instructions in order' do
|
44
|
+
inst1 = Tataru::Instruction.new
|
45
|
+
inst2 = Tataru::Instruction.new
|
46
|
+
|
47
|
+
runner = Tataru::Runner.new([
|
48
|
+
inst1, inst2
|
49
|
+
])
|
50
|
+
|
51
|
+
expect(inst1).to receive(:run)
|
52
|
+
runner.run_next
|
53
|
+
|
54
|
+
expect(inst2).to receive(:run)
|
55
|
+
runner.run_next
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'records resource instructions' do
|
59
|
+
inst1cls = Class.new(Tataru::Instructions::ResourceInstruction)
|
60
|
+
stub_const('TestInstruction', inst1cls)
|
61
|
+
inst1 = inst1cls.new
|
62
|
+
|
63
|
+
|
64
|
+
runner = Tataru::Runner.new([
|
65
|
+
inst1
|
66
|
+
])
|
67
|
+
|
68
|
+
runner.memory.hash[:temp][:resource_name] = 'abcd'
|
69
|
+
runner.run_next
|
70
|
+
|
71
|
+
expect(runner.oplog).to eq [
|
72
|
+
{
|
73
|
+
operation: 'TEST',
|
74
|
+
resource: 'abcd'
|
75
|
+
}
|
76
|
+
]
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'ignores tataru instruction namespace' do
|
80
|
+
inst1cls = Class.new(Tataru::Instructions::ResourceInstruction)
|
81
|
+
stub_const('Tataru::Instructions::TestInstruction', inst1cls)
|
82
|
+
inst1 = inst1cls.new
|
83
|
+
|
84
|
+
|
85
|
+
runner = Tataru::Runner.new([
|
86
|
+
inst1
|
87
|
+
])
|
88
|
+
|
89
|
+
runner.memory.hash[:temp][:resource_name] = 'defg'
|
90
|
+
runner.run_next
|
91
|
+
|
92
|
+
expect(runner.oplog).to eq [
|
93
|
+
{
|
94
|
+
operation: 'TEST',
|
95
|
+
resource: 'defg'
|
96
|
+
}
|
97
|
+
]
|
98
|
+
end
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,401 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'pry'
|
5
|
+
require 'active_support/testing/time_helpers'
|
6
|
+
require 'tataru'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.include ActiveSupport::Testing::TimeHelpers
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestEnvironment
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
attr_accessor :files, :servers, :ip_addresses
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
clear!
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear!
|
22
|
+
# ids decided by user
|
23
|
+
@domain_names = {}
|
24
|
+
# a resource with mutable fields
|
25
|
+
@load_balancers = {}
|
26
|
+
# a resource with outputs
|
27
|
+
@api_keys = {}
|
28
|
+
|
29
|
+
# a resource
|
30
|
+
@files = {}
|
31
|
+
# a resource that overlaps
|
32
|
+
@servers = []
|
33
|
+
|
34
|
+
# ids decided by server
|
35
|
+
@ip_addresses = {}
|
36
|
+
@server_to_ip_map = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_file(name, contents)
|
40
|
+
raise 'Duplicate file name' if @files.key? name
|
41
|
+
|
42
|
+
@files[name] = {
|
43
|
+
name: name,
|
44
|
+
contents: contents,
|
45
|
+
updated_at: Time.now.utc.to_s,
|
46
|
+
created_at: Time.now.utc.to_s
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_file(name, contents)
|
51
|
+
raise "Nonexistent file '#{name}'" unless @files.key? name
|
52
|
+
|
53
|
+
@files[name][:contents] = contents
|
54
|
+
@files[name][:updated_at] = Time.now.utc.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete_file(name)
|
58
|
+
raise "Nonexistent file '#{name}'" unless @files.key? name
|
59
|
+
|
60
|
+
@files.delete name
|
61
|
+
end
|
62
|
+
|
63
|
+
def exists_file?(name)
|
64
|
+
@files.key? name
|
65
|
+
end
|
66
|
+
|
67
|
+
def file(name)
|
68
|
+
raise "Nonexistent file '#{name}'" unless @files.key? name
|
69
|
+
|
70
|
+
@files[name]
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def create_server(size)
|
75
|
+
id = @servers.count
|
76
|
+
@servers[id] = {
|
77
|
+
size: size,
|
78
|
+
created_at: Time.now.utc.to_s
|
79
|
+
}
|
80
|
+
"server#{id}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_server(server_id)
|
84
|
+
id = server_id.sub(/^server/, '').to_i
|
85
|
+
raise "Nonexistent server '#{server_id}'" if @servers[id].nil?
|
86
|
+
|
87
|
+
ip = @server_to_ip_map[server_id]
|
88
|
+
if ip != nil
|
89
|
+
raise "Cannot delete while connected to #{ip}"
|
90
|
+
end
|
91
|
+
|
92
|
+
@servers[id] = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def exists_server?(server_id)
|
96
|
+
id = server_id.sub(/^server/, '').to_i
|
97
|
+
@servers.key? id
|
98
|
+
end
|
99
|
+
|
100
|
+
def server(server_id)
|
101
|
+
id = server_id.sub(/^server/, '').to_i
|
102
|
+
raise "Nonexistent server '#{server_id}'" if @servers[id].nil?
|
103
|
+
|
104
|
+
@servers[id]
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_ip(server_id)
|
108
|
+
id = server_id.sub(/^server/, '').to_i
|
109
|
+
raise "Nonexistent server '#{server_id}'" if @servers[id].nil?
|
110
|
+
|
111
|
+
(2..254).each do |i|
|
112
|
+
addr = "2.3.4.#{i}"
|
113
|
+
next if @ip_addresses.key? addr
|
114
|
+
|
115
|
+
return modify_ip(addr, server_id)
|
116
|
+
end
|
117
|
+
|
118
|
+
raise 'No more ips available'
|
119
|
+
end
|
120
|
+
|
121
|
+
def modify_ip(addr, server_id)
|
122
|
+
id = server_id.sub(/^server/, '').to_i
|
123
|
+
raise "Nonexistent server '#{server_id}'" if @servers[id].nil?
|
124
|
+
raise "Nonexistent IP #{addr}" if !@ip_addresses.key? addr
|
125
|
+
|
126
|
+
@ip_addresses[addr] = server_id
|
127
|
+
@server_to_ip_map[server_id] = addr
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete_ip(addr)
|
131
|
+
raise "Nonexistent IP #{addr}" if !@ip_addresses.key? addr
|
132
|
+
|
133
|
+
server_id = @ip_addresses[addr]
|
134
|
+
@server_to_ip_map.delete(server_id)
|
135
|
+
@ip_addresses.delete(addr)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# base class of resource
|
140
|
+
class TestFileResource < Tataru::BaseResource
|
141
|
+
def create(params)
|
142
|
+
TestEnvironment.instance.create_file(params[:name], params[:contents])
|
143
|
+
@remote_id = params[:name]
|
144
|
+
end
|
145
|
+
|
146
|
+
def read(name_array)
|
147
|
+
results = {}
|
148
|
+
TestEnvironment.instance.file(@remote_id).each do |k, v|
|
149
|
+
results[k] = v if name_array.include? k
|
150
|
+
end
|
151
|
+
results
|
152
|
+
end
|
153
|
+
|
154
|
+
def update(params)
|
155
|
+
TestEnvironment.instance.update_file(@remote_id, params[:contents])
|
156
|
+
end
|
157
|
+
|
158
|
+
def delete
|
159
|
+
TestEnvironment.instance.delete_file(@remote_id)
|
160
|
+
end
|
161
|
+
|
162
|
+
def outputs
|
163
|
+
TestEnvironment.instance.file(@remote_id)
|
164
|
+
end
|
165
|
+
|
166
|
+
def create_complete?
|
167
|
+
# check if creation is complete
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
def update_complete?
|
172
|
+
# check if update is complete
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
def delete_complete?
|
177
|
+
# check if delete is complete
|
178
|
+
true
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# description of a resource
|
183
|
+
class TestFileResourceDesc < Tataru::BaseResourceDesc
|
184
|
+
def resource_class
|
185
|
+
TestFileResource
|
186
|
+
end
|
187
|
+
|
188
|
+
def mutable_fields
|
189
|
+
[:contents]
|
190
|
+
end
|
191
|
+
|
192
|
+
def immutable_fields
|
193
|
+
[:name]
|
194
|
+
end
|
195
|
+
|
196
|
+
def required_fields
|
197
|
+
[:name]
|
198
|
+
end
|
199
|
+
|
200
|
+
def output_fields
|
201
|
+
[:created_at, :updated_at]
|
202
|
+
end
|
203
|
+
|
204
|
+
def needs_remote_id?
|
205
|
+
true
|
206
|
+
end
|
207
|
+
|
208
|
+
def delete_at_end?
|
209
|
+
false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# base class of resource
|
214
|
+
class TestServerResource < Tataru::BaseResource
|
215
|
+
def create(params)
|
216
|
+
@remote_id = TestEnvironment.instance.create_server(params[:size])
|
217
|
+
end
|
218
|
+
|
219
|
+
def read(name_array)
|
220
|
+
results = {}
|
221
|
+
TestEnvironment.instance.server(@remote_id).each do |k, v|
|
222
|
+
results[k] = v if name_array.include? k
|
223
|
+
end
|
224
|
+
results
|
225
|
+
end
|
226
|
+
|
227
|
+
def update(params)
|
228
|
+
end
|
229
|
+
|
230
|
+
def delete
|
231
|
+
TestEnvironment.instance.delete_server(@remote_id)
|
232
|
+
end
|
233
|
+
|
234
|
+
def outputs
|
235
|
+
TestEnvironment.instance.server(@remote_id)
|
236
|
+
end
|
237
|
+
|
238
|
+
def create_complete?
|
239
|
+
# check if creation is complete
|
240
|
+
true
|
241
|
+
end
|
242
|
+
|
243
|
+
def update_complete?
|
244
|
+
# check if update is complete
|
245
|
+
true
|
246
|
+
end
|
247
|
+
|
248
|
+
def delete_complete?
|
249
|
+
# check if delete is complete
|
250
|
+
true
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# description of a resource
|
255
|
+
class TestServerResourceDesc < Tataru::BaseResourceDesc
|
256
|
+
def resource_class
|
257
|
+
TestServerResource
|
258
|
+
end
|
259
|
+
|
260
|
+
def mutable_fields
|
261
|
+
[]
|
262
|
+
end
|
263
|
+
|
264
|
+
def immutable_fields
|
265
|
+
[:size]
|
266
|
+
end
|
267
|
+
|
268
|
+
def required_fields
|
269
|
+
[:size]
|
270
|
+
end
|
271
|
+
|
272
|
+
def output_fields
|
273
|
+
[:created_at]
|
274
|
+
end
|
275
|
+
|
276
|
+
def needs_remote_id?
|
277
|
+
true
|
278
|
+
end
|
279
|
+
|
280
|
+
def delete_at_end?
|
281
|
+
true
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# string joiner
|
286
|
+
class StringJoinerResource < Tataru::BaseResource
|
287
|
+
attr_reader :remote_id
|
288
|
+
|
289
|
+
def initialize(remote_id)
|
290
|
+
@remote_id = remote_id
|
291
|
+
end
|
292
|
+
|
293
|
+
def create(params)
|
294
|
+
@remote_id = params[:strings].join("\n")
|
295
|
+
end
|
296
|
+
|
297
|
+
def outputs
|
298
|
+
{
|
299
|
+
result: @remote_id
|
300
|
+
}
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# description of a StringJoinerResource
|
305
|
+
class StringJoinerResourceDesc < Tataru::BaseResourceDesc
|
306
|
+
def resource_class
|
307
|
+
StringJoinerResource
|
308
|
+
end
|
309
|
+
|
310
|
+
def immutable_fields
|
311
|
+
[:strings]
|
312
|
+
end
|
313
|
+
|
314
|
+
def output_fields
|
315
|
+
[:result]
|
316
|
+
end
|
317
|
+
|
318
|
+
def required_fields
|
319
|
+
[:strings]
|
320
|
+
end
|
321
|
+
|
322
|
+
def needs_remote_id?
|
323
|
+
true
|
324
|
+
end
|
325
|
+
|
326
|
+
def delete_at_end?
|
327
|
+
false
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
class TestIpAddressResource < Tataru::BaseResource
|
332
|
+
def create(params)
|
333
|
+
ip = TestEnvironment.instance.create_ip(params[:server_id])
|
334
|
+
@remote_id = ip
|
335
|
+
end
|
336
|
+
|
337
|
+
def read(name_array)
|
338
|
+
{
|
339
|
+
server_id: TestEnvironment.instance.ip_addresses[@remote_id]
|
340
|
+
}
|
341
|
+
end
|
342
|
+
|
343
|
+
def update(params)
|
344
|
+
TestEnvironment.instance.modify_ip(@remote_id, params[:server_id])
|
345
|
+
end
|
346
|
+
|
347
|
+
def delete
|
348
|
+
TestEnvironment.instance.delete_ip(@remote_id)
|
349
|
+
end
|
350
|
+
|
351
|
+
def outputs
|
352
|
+
{
|
353
|
+
server_id: TestEnvironment.instance.ip_addresses[@remote_id]
|
354
|
+
}
|
355
|
+
end
|
356
|
+
|
357
|
+
def create_complete?
|
358
|
+
# check if creation is complete
|
359
|
+
true
|
360
|
+
end
|
361
|
+
|
362
|
+
def update_complete?
|
363
|
+
# check if update is complete
|
364
|
+
true
|
365
|
+
end
|
366
|
+
|
367
|
+
def delete_complete?
|
368
|
+
# check if delete is complete
|
369
|
+
true
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
class TestIpAddressResourceDesc < Tataru::BaseResourceDesc
|
374
|
+
def resource_class
|
375
|
+
TestIpAddressResource
|
376
|
+
end
|
377
|
+
|
378
|
+
def mutable_fields
|
379
|
+
[:server_id]
|
380
|
+
end
|
381
|
+
|
382
|
+
def immutable_fields
|
383
|
+
[]
|
384
|
+
end
|
385
|
+
|
386
|
+
def required_fields
|
387
|
+
[:server_id]
|
388
|
+
end
|
389
|
+
|
390
|
+
def output_fields
|
391
|
+
[:ip]
|
392
|
+
end
|
393
|
+
|
394
|
+
def needs_remote_id?
|
395
|
+
true
|
396
|
+
end
|
397
|
+
|
398
|
+
def delete_at_end?
|
399
|
+
false
|
400
|
+
end
|
401
|
+
end
|