cosmos 3.0.0 → 3.0.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 +4 -4
- data/.travis.yml +2 -0
- data/CONTRIBUTING.txt +50 -0
- data/Gemfile +1 -0
- data/Manifest.txt +2 -0
- data/README.md +66 -1
- data/cosmos.gemspec +2 -3
- data/data/crc.txt +2 -2
- data/demo/Gemfile +1 -0
- data/ext/cosmos/ext/platform/platform.c +1 -2
- data/ext/mkrf_conf.rb +40 -0
- data/install/Gemfile +1 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +1 -0
- data/lib/cosmos/version.rb +4 -4
- data/spec/core_ext/io_spec.rb +1 -1
- data/spec/core_ext/math_spec.rb +2 -2
- data/spec/interfaces/interface_spec.rb +3 -0
- data/spec/interfaces/udp_interface_spec.rb +2 -0
- data/spec/io/json_drb_object_spec.rb +6 -0
- data/spec/io/json_drb_spec.rb +18 -0
- data/spec/io/tcpip_server_spec.rb +28 -12
- data/spec/packet_logs/packet_log_writer_spec.rb +25 -11
- data/spec/script/script_spec.rb +8 -4
- data/spec/spec_helper.rb +11 -1
- data/spec/tools/cmd_tlm_server/api_spec.rb +33 -10
- data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +9 -10
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +5 -0
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +16 -19
- data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +38 -49
- data/spec/tools/cmd_tlm_server/interfaces_spec.rb +17 -7
- data/spec/tools/cmd_tlm_server/router_thread_spec.rb +9 -12
- data/spec/tools/cmd_tlm_server/routers_spec.rb +17 -5
- data/spec/top_level/top_level_spec.rb +4 -3
- metadata +7 -18
@@ -28,6 +28,7 @@ module Cosmos
|
|
28
28
|
sleep 0.2
|
29
29
|
expect { server.connect }.to raise_error(/Error binding/)
|
30
30
|
server.disconnect
|
31
|
+
sleep(0.2)
|
31
32
|
end
|
32
33
|
|
33
34
|
it "should create a listener thread for the read port" do
|
@@ -94,10 +95,11 @@ module Cosmos
|
|
94
95
|
server = TcpipServer.new(8888,8888,nil,nil,'Burst')
|
95
96
|
server.connect
|
96
97
|
sleep 0.2
|
97
|
-
socket = TCPSocket.open("127.0.0.
|
98
|
+
socket = TCPSocket.open("127.0.0.1",8888)
|
98
99
|
sleep 0.2
|
99
100
|
server.disconnect
|
100
101
|
socket.close
|
102
|
+
sleep(0.2)
|
101
103
|
|
102
104
|
stdout.string.should match /Tcpip server listen thread unexpectedly died/
|
103
105
|
end
|
@@ -108,6 +110,8 @@ module Cosmos
|
|
108
110
|
it "should return nil if there is no read port" do
|
109
111
|
server = TcpipServer.new(8888,nil,nil,nil,'Burst')
|
110
112
|
server.read.should be_nil
|
113
|
+
server.disconnect
|
114
|
+
sleep(0.2)
|
111
115
|
end
|
112
116
|
|
113
117
|
#~ it "should block if no data is available" do
|
@@ -123,7 +127,7 @@ module Cosmos
|
|
123
127
|
server.connect
|
124
128
|
sleep 0.2
|
125
129
|
|
126
|
-
socket = TCPSocket.open("127.0.0.
|
130
|
+
socket = TCPSocket.open("127.0.0.1",8889)
|
127
131
|
socket.write("\x00\x01")
|
128
132
|
sleep 0.2
|
129
133
|
server.num_clients.should eql 1
|
@@ -133,6 +137,7 @@ module Cosmos
|
|
133
137
|
sleep 0.2
|
134
138
|
server.num_clients.should eql 0
|
135
139
|
socket.close
|
140
|
+
sleep(0.2)
|
136
141
|
end
|
137
142
|
|
138
143
|
it "should check the client against the ACL" do
|
@@ -144,13 +149,14 @@ module Cosmos
|
|
144
149
|
server = TcpipServer.new(nil,8889,nil,nil,'Burst')
|
145
150
|
server.connect
|
146
151
|
sleep 0.2
|
147
|
-
socket = TCPSocket.open("127.0.0.
|
152
|
+
socket = TCPSocket.open("127.0.0.1",8889)
|
148
153
|
sleep 0.2
|
149
154
|
server.num_clients.should eql 0
|
150
155
|
socket.eof?.should be_truthy
|
151
156
|
server.disconnect
|
152
157
|
sleep 0.2
|
153
158
|
socket.close
|
159
|
+
sleep(0.2)
|
154
160
|
|
155
161
|
stdout.string.should match /Tcpip server rejected connection/
|
156
162
|
end
|
@@ -166,12 +172,13 @@ module Cosmos
|
|
166
172
|
server = TcpipServer.new(nil,8889,nil,nil,'Burst')
|
167
173
|
server.connect
|
168
174
|
sleep 0.2
|
169
|
-
socket = TCPSocket.open("127.0.0.
|
175
|
+
socket = TCPSocket.open("127.0.0.1",8889)
|
170
176
|
sleep 0.2
|
171
177
|
server.disconnect
|
172
178
|
sleep 0.2
|
173
179
|
server.num_clients.should eql 0
|
174
180
|
socket.close
|
181
|
+
sleep(0.2)
|
175
182
|
|
176
183
|
stdout.string.should match /Tcpip server read thread unexpectedly died/
|
177
184
|
end
|
@@ -186,11 +193,12 @@ module Cosmos
|
|
186
193
|
server = TcpipServer.new(8888,8888,nil,nil,'Burst')
|
187
194
|
server.connect
|
188
195
|
sleep 0.2
|
189
|
-
socket = TCPSocket.open("127.0.0.
|
196
|
+
socket = TCPSocket.open("127.0.0.1",8888)
|
190
197
|
socket.write("\x00\x01")
|
191
198
|
sleep 0.2
|
192
199
|
server.disconnect
|
193
200
|
socket.close
|
201
|
+
sleep(0.2)
|
194
202
|
|
195
203
|
stdout.string.should match /Tcpip server read thread unexpectedly died/
|
196
204
|
end
|
@@ -211,7 +219,7 @@ module Cosmos
|
|
211
219
|
server.connect
|
212
220
|
sleep 0.2
|
213
221
|
|
214
|
-
socket = TCPSocket.open("127.0.0.
|
222
|
+
socket = TCPSocket.open("127.0.0.1",8888)
|
215
223
|
sleep 0.2
|
216
224
|
server.num_clients.should eql 1
|
217
225
|
packet = Packet.new("TGT","PKT")
|
@@ -225,6 +233,7 @@ module Cosmos
|
|
225
233
|
sleep 0.2
|
226
234
|
server.num_clients.should eql 0
|
227
235
|
socket.close
|
236
|
+
sleep(0.2)
|
228
237
|
end
|
229
238
|
|
230
239
|
it "should log an error if the write thread dies" do
|
@@ -240,7 +249,7 @@ module Cosmos
|
|
240
249
|
server = MyTcpipServer3.new(8888,nil,nil,nil,'Burst')
|
241
250
|
server.connect
|
242
251
|
sleep 0.2
|
243
|
-
socket = TCPSocket.open("127.0.0.
|
252
|
+
socket = TCPSocket.open("127.0.0.1",8888)
|
244
253
|
sleep 0.2
|
245
254
|
server.num_clients.should eql 1
|
246
255
|
server.write(Packet.new("TGT","PKT"))
|
@@ -248,6 +257,7 @@ module Cosmos
|
|
248
257
|
server.num_clients.should eql 0
|
249
258
|
server.disconnect
|
250
259
|
socket.close
|
260
|
+
sleep(0.2)
|
251
261
|
|
252
262
|
stdout.string.should match /Tcpip server write thread unexpectedly died/
|
253
263
|
end
|
@@ -264,11 +274,12 @@ module Cosmos
|
|
264
274
|
server.connect
|
265
275
|
server.num_clients.should eql 0
|
266
276
|
sleep 0.2
|
267
|
-
socket = TCPSocket.open("127.0.0.
|
277
|
+
socket = TCPSocket.open("127.0.0.1",8888)
|
268
278
|
sleep 0.2
|
269
279
|
server.num_clients.should eql 0
|
270
280
|
server.disconnect
|
271
281
|
socket.close
|
282
|
+
sleep(0.2)
|
272
283
|
|
273
284
|
stdout.string.should match /Tcpip server lost write connection/
|
274
285
|
end
|
@@ -282,11 +293,11 @@ module Cosmos
|
|
282
293
|
|
283
294
|
server = TcpipServer.new(8888,8889,nil,nil,'Burst')
|
284
295
|
server.connect
|
285
|
-
sleep 0.
|
296
|
+
sleep 0.5
|
286
297
|
|
287
|
-
socket1 = TCPSocket.open("127.0.0.
|
288
|
-
socket2 = TCPSocket.open("127.0.0.
|
289
|
-
sleep 0.
|
298
|
+
socket1 = TCPSocket.open("127.0.0.1",8888)
|
299
|
+
socket2 = TCPSocket.open("127.0.0.1",8889)
|
300
|
+
sleep 0.5
|
290
301
|
server.num_clients.should eql 2
|
291
302
|
packet = Packet.new("TGT","PKT")
|
292
303
|
packet.buffer = "\x01\x02\x03\x04"
|
@@ -297,6 +308,7 @@ module Cosmos
|
|
297
308
|
server.disconnect
|
298
309
|
socket1.close
|
299
310
|
socket2.close
|
311
|
+
sleep(0.2)
|
300
312
|
|
301
313
|
stdout.string.should match /Tcpip server lost write connection/
|
302
314
|
end
|
@@ -307,6 +319,8 @@ module Cosmos
|
|
307
319
|
it "should return 0 if there is no read port" do
|
308
320
|
server = TcpipServer.new(8888,nil,nil,nil,'Burst')
|
309
321
|
server.read_queue_size.should eql 0
|
322
|
+
server.disconnect
|
323
|
+
sleep(0.2)
|
310
324
|
end
|
311
325
|
end
|
312
326
|
|
@@ -314,6 +328,8 @@ module Cosmos
|
|
314
328
|
it "should return 0 if there is no write port" do
|
315
329
|
server = TcpipServer.new(nil,8889,nil,nil,'Burst')
|
316
330
|
server.write_queue_size.should eql 0
|
331
|
+
server.disconnect
|
332
|
+
sleep(0.2)
|
317
333
|
end
|
318
334
|
end
|
319
335
|
|
@@ -32,33 +32,37 @@ module Cosmos
|
|
32
32
|
it "should create a command log writer" do
|
33
33
|
plw = PacketLogWriter.new(:CMD,nil,true,nil,10000000,nil,false)
|
34
34
|
plw.write(Packet.new('',''))
|
35
|
-
plw.
|
35
|
+
plw.shutdown
|
36
36
|
expect(Dir[File.join(@log_path,"*.bin")][-1]).to match("_cmd.bin")
|
37
|
+
sleep(0.1)
|
37
38
|
end
|
38
39
|
|
39
40
|
it "should create a telemetry log writer" do
|
40
41
|
plw = PacketLogWriter.new(:TLM,nil,true,nil,10000000,nil,false)
|
41
42
|
plw.write(Packet.new('',''))
|
42
|
-
plw.
|
43
|
+
plw.shutdown
|
43
44
|
expect(Dir[File.join(@log_path,"*.bin")][-1]).to match("_tlm.bin")
|
45
|
+
sleep(0.1)
|
44
46
|
end
|
45
47
|
|
46
48
|
it "should use log_name in the filename" do
|
47
49
|
plw = PacketLogWriter.new(:TLM,'test',true,nil,10000000,nil,false)
|
48
50
|
|
49
51
|
plw.write(Packet.new('',''))
|
50
|
-
plw.
|
52
|
+
plw.shutdown
|
51
53
|
expect(Dir[File.join(@log_path,"*.bin")][-1]).to match("testtlm.bin")
|
54
|
+
sleep(0.1)
|
52
55
|
end
|
53
56
|
|
54
57
|
it "should use the log directory" do
|
55
58
|
plw = PacketLogWriter.new(:TLM,'packet_log_writer_spec_',true,nil,10000000,Cosmos::USERPATH,false)
|
56
59
|
plw.write(Packet.new('',''))
|
57
|
-
plw.
|
60
|
+
plw.shutdown
|
58
61
|
expect(Dir[File.join(Cosmos::USERPATH,"*packet_log_writer_spec*")][-1]).to match("_tlm.bin")
|
59
62
|
Dir[File.join(Cosmos::USERPATH,"*packet_log_writer_spec*")].each do |file|
|
60
63
|
File.delete file
|
61
64
|
end
|
65
|
+
sleep(0.1)
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
@@ -68,12 +72,13 @@ module Cosmos
|
|
68
72
|
pkt = Packet.new('tgt','pkt')
|
69
73
|
pkt.buffer = "\x01\x02\x03\x04"
|
70
74
|
plw.write(pkt)
|
71
|
-
plw.
|
75
|
+
plw.shutdown
|
72
76
|
data = nil
|
73
77
|
File.open(Dir[File.join(@log_path,"*.bin")][-1],'rb') do |file|
|
74
78
|
data = file.read
|
75
79
|
end
|
76
80
|
data[-4..-1].should eql "\x01\x02\x03\x04"
|
81
|
+
sleep(0.1)
|
77
82
|
end
|
78
83
|
|
79
84
|
it "should not write packets if logging is disabled" do
|
@@ -81,8 +86,9 @@ module Cosmos
|
|
81
86
|
pkt = Packet.new('tgt','pkt')
|
82
87
|
pkt.buffer = "\x01\x02\x03\x04"
|
83
88
|
plw.write(pkt)
|
84
|
-
plw.
|
89
|
+
plw.shutdown
|
85
90
|
Dir[File.join(@log_path,"*.bin")].should be_empty
|
91
|
+
sleep(0.1)
|
86
92
|
end
|
87
93
|
|
88
94
|
it "should cycle the log when it a size" do
|
@@ -98,7 +104,8 @@ module Cosmos
|
|
98
104
|
# This write pushs us past 200 so we should start a new file
|
99
105
|
plw.write(pkt)
|
100
106
|
Dir[File.join(@log_path,"*.bin")].length.should eql 2
|
101
|
-
plw.
|
107
|
+
plw.shutdown
|
108
|
+
sleep(0.1)
|
102
109
|
end
|
103
110
|
|
104
111
|
it "should cycle the log after a set time" do
|
@@ -126,10 +133,11 @@ module Cosmos
|
|
126
133
|
log1_seconds = files[0].split('_')[-3].to_i * 60 + files[0].split('_')[-2].to_i
|
127
134
|
log2_seconds = files[1].split('_')[-3].to_i * 60 + files[1].split('_')[-2].to_i
|
128
135
|
(log2_seconds - log1_seconds).should be_within(2).of(3)
|
129
|
-
plw.
|
136
|
+
plw.shutdown
|
130
137
|
# Monkey patch the constant back to the default
|
131
138
|
PacketLogWriter.__send__(:remove_const,:CYCLE_TIME_INTERVAL)
|
132
139
|
PacketLogWriter.const_set(:CYCLE_TIME_INTERVAL, 2)
|
140
|
+
sleep(0.1)
|
133
141
|
end
|
134
142
|
|
135
143
|
it "should write asynchronously to a log" do
|
@@ -146,6 +154,7 @@ module Cosmos
|
|
146
154
|
end
|
147
155
|
data[-4..-1].should eql "\x01\x02\x03\x04"
|
148
156
|
plw.shutdown
|
157
|
+
sleep(0.1)
|
149
158
|
end
|
150
159
|
|
151
160
|
it "should handle errors creating the log file" do
|
@@ -159,6 +168,7 @@ module Cosmos
|
|
159
168
|
plw.stop
|
160
169
|
stdout.string.should match "Error opening"
|
161
170
|
plw.shutdown
|
171
|
+
sleep(0.1)
|
162
172
|
end
|
163
173
|
end
|
164
174
|
|
@@ -173,6 +183,7 @@ module Cosmos
|
|
173
183
|
plw.stop
|
174
184
|
stdout.string.should match "Error closing"
|
175
185
|
plw.shutdown
|
186
|
+
sleep(0.1)
|
176
187
|
end
|
177
188
|
end
|
178
189
|
end
|
@@ -182,25 +193,28 @@ module Cosmos
|
|
182
193
|
plw = PacketLogWriter.new(:TLM,nil,false,nil,10000000,nil,false)
|
183
194
|
plw.start
|
184
195
|
plw.write(Packet.new('',''))
|
185
|
-
plw.
|
196
|
+
plw.shutdown
|
186
197
|
file = Dir[File.join(@log_path,"*.bin")][-1]
|
187
198
|
File.size(file).should_not eql 0
|
199
|
+
sleep(0.1)
|
188
200
|
end
|
189
201
|
|
190
202
|
it "should add a label to the log file" do
|
191
203
|
plw = PacketLogWriter.new(:TLM,nil,false,nil,10000000,nil,false)
|
192
204
|
plw.start('test')
|
193
205
|
plw.write(Packet.new('',''))
|
194
|
-
plw.
|
206
|
+
plw.shutdown
|
195
207
|
expect(Dir[File.join(@log_path,"*.bin")][-1]).to match("_tlm_test.bin")
|
208
|
+
sleep(0.1)
|
196
209
|
end
|
197
210
|
|
198
211
|
it "should ignore bad label formats" do
|
199
212
|
plw = PacketLogWriter.new(:TLM,nil,false,nil,10000000,nil,false)
|
200
213
|
plw.start('my_test')
|
201
214
|
plw.write(Packet.new('',''))
|
202
|
-
plw.
|
215
|
+
plw.shutdown
|
203
216
|
expect(Dir[File.join(@log_path,"*.bin")][-1]).to match("_tlm.bin")
|
217
|
+
sleep(0.1)
|
204
218
|
end
|
205
219
|
end
|
206
220
|
|
data/spec/script/script_spec.rb
CHANGED
@@ -41,18 +41,22 @@ module Cosmos
|
|
41
41
|
allow_any_instance_of(Interface).to receive(:read)
|
42
42
|
|
43
43
|
@server = CmdTlmServer.new
|
44
|
+
shutdown_cmd_tlm()
|
45
|
+
initialize_script_module()
|
44
46
|
sleep 0.1
|
45
47
|
end
|
46
48
|
|
47
49
|
after(:each) do
|
48
50
|
@server.stop
|
51
|
+
shutdown_cmd_tlm()
|
52
|
+
sleep(0.1)
|
49
53
|
end
|
50
54
|
|
51
55
|
describe "cmd" do
|
52
56
|
it "should send a command" do
|
53
57
|
capture_io do |stdout|
|
54
58
|
cmd("INST ABORT")
|
55
|
-
stdout.string.should match /cmd\(\'INST ABORT\'\)/
|
59
|
+
stdout.string.should match /cmd\(\'INST ABORT\'\)/ #'
|
56
60
|
end
|
57
61
|
end
|
58
62
|
|
@@ -136,7 +140,7 @@ module Cosmos
|
|
136
140
|
end
|
137
141
|
|
138
142
|
it "should check parameter ranges" do
|
139
|
-
expect { cmd_raw("INST COLLECT with TYPE 0, DURATION 20") }.to raise_error(/Command parameter 'INST COLLECT DURATION' = 20 not in valid range/)
|
143
|
+
expect { cmd_raw("INST COLLECT with TYPE 0, DURATION 20") }.to raise_error(/Command parameter 'INST COLLECT DURATION' = 20 not in valid range/) #'
|
140
144
|
end
|
141
145
|
|
142
146
|
it "should prompt for a hazardous command" do
|
@@ -189,7 +193,7 @@ module Cosmos
|
|
189
193
|
capture_io do |stdout|
|
190
194
|
cmd_raw_no_hazardous_check("INST COLLECT with TYPE 1")
|
191
195
|
stdout.string.should match "Command INST COLLECT being sent ignoring hazardous warnings"
|
192
|
-
stdout.string.should match /cmd_raw\(\'INST COLLECT/
|
196
|
+
stdout.string.should match /cmd_raw\(\'INST COLLECT/ #'
|
193
197
|
end
|
194
198
|
end
|
195
199
|
end
|
@@ -199,7 +203,7 @@ module Cosmos
|
|
199
203
|
capture_io do |stdout|
|
200
204
|
cmd_raw_no_checks("INST COLLECT with TYPE 1, DURATION 20")
|
201
205
|
stdout.string.should match "Command INST COLLECT being sent ignoring hazardous warnings"
|
202
|
-
stdout.string.should match /cmd_raw\(\'INST COLLECT/
|
206
|
+
stdout.string.should match /cmd_raw\(\'INST COLLECT/ #'
|
203
207
|
end
|
204
208
|
end
|
205
209
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -74,9 +74,19 @@ RSpec.configure do |config|
|
|
74
74
|
Cosmos.disable_warnings do
|
75
75
|
Object.const_set(:STDOUT, $saved_stdout_const)
|
76
76
|
end
|
77
|
+
# Kill any leftover threads
|
78
|
+
if Thread.list.length > 1
|
79
|
+
Thread.list.each do |t|
|
80
|
+
t.kill if t != Thread.current
|
81
|
+
end
|
82
|
+
sleep(0.2)
|
83
|
+
end
|
77
84
|
end
|
78
85
|
|
79
|
-
|
86
|
+
config.after(:each) do
|
87
|
+
# Make sure we didn't leave any lingering threads
|
88
|
+
Thread.list.length.should eql(1)
|
89
|
+
end
|
80
90
|
end
|
81
91
|
|
82
92
|
# Clean up the spec configuration directory
|
@@ -27,21 +27,43 @@ module Cosmos
|
|
27
27
|
end
|
28
28
|
|
29
29
|
before(:each) do
|
30
|
-
allow_any_instance_of(Interface).to receive(:connected?)
|
31
|
-
allow_any_instance_of(Interface).to receive(:connect)
|
32
|
-
allow_any_instance_of(Interface).to receive(:disconnect)
|
33
|
-
allow_any_instance_of(Interface).to receive(:write_raw)
|
34
|
-
allow_any_instance_of(Interface).to receive(:read)
|
35
30
|
@api = CmdTlmServer.new
|
31
|
+
config = @api.interfaces.instance_variable_get(:@config)
|
32
|
+
# Stub interfaces and routers. Do this like this so that they can still be used in an after hook
|
33
|
+
config.interfaces.each do |interface_name, i|
|
34
|
+
if i.class == Cosmos::Interface
|
35
|
+
def i.connected?(*args)
|
36
|
+
end
|
37
|
+
def i.connect(*args)
|
38
|
+
end
|
39
|
+
def i.disconnect(*args)
|
40
|
+
end
|
41
|
+
def i.write_raw(*args)
|
42
|
+
end
|
43
|
+
def i.read(*args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
config.routers.each do |router_name, i|
|
48
|
+
if i.class == Cosmos::Interface
|
49
|
+
def i.connected?(*args)
|
50
|
+
end
|
51
|
+
def i.connect(*args)
|
52
|
+
end
|
53
|
+
def i.disconnect(*args)
|
54
|
+
end
|
55
|
+
def i.write_raw(*args)
|
56
|
+
end
|
57
|
+
def i.read(*args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
36
61
|
allow(@api.commanding).to receive(:send_command_to_target)
|
37
62
|
end
|
38
63
|
|
39
64
|
after(:each) do
|
40
|
-
|
41
|
-
|
42
|
-
rescue
|
43
|
-
# Ignore all errors when trying to stop
|
44
|
-
end
|
65
|
+
@api.stop
|
66
|
+
sleep(0.2)
|
45
67
|
end
|
46
68
|
|
47
69
|
after(:all) do
|
@@ -61,6 +83,7 @@ module Cosmos
|
|
61
83
|
describe "cmd" do
|
62
84
|
it "should complain about unknown targets, commands, and parameters" do
|
63
85
|
test_cmd_unknown(:cmd)
|
86
|
+
sleep(0.5)
|
64
87
|
end
|
65
88
|
|
66
89
|
it "should process a string" do
|