ruby-atmos 0.6.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.
- data/COPYING +8 -0
- data/README +129 -0
- data/Rakefile +94 -0
- data/lib/atmos/attributes.rb +545 -0
- data/lib/atmos/exceptions.rb +28 -0
- data/lib/atmos/object.rb +263 -0
- data/lib/atmos/parser.rb +110 -0
- data/lib/atmos/request.rb +188 -0
- data/lib/atmos/rest.rb +254 -0
- data/lib/atmos/store.rb +209 -0
- data/lib/atmos/util.rb +40 -0
- data/lib/atmos/version.rb +12 -0
- data/test/credentials.rb +141 -0
- data/test/esutest.rb +499 -0
- data/test/files/SmallImageForTest.iso +0 -0
- data/test/files/dragaf-tiny-from-vsphere.ova +0 -0
- data/test/files/something.txt +1 -0
- data/test/request_test.rb +50 -0
- data/test/suite.rb +9 -0
- data/test/suite_noproxy.rb +12 -0
- data/test/suite_proxy.rb +14 -0
- data/test/test_acl.rb +283 -0
- data/test/test_metadata.rb +162 -0
- data/test/test_object_create.rb +152 -0
- data/test/test_object_misc.rb +80 -0
- data/test/test_object_read.rb +114 -0
- data/test/test_object_update.rb +58 -0
- data/test/test_store.rb +125 -0
- data/test/test_util.rb +58 -0
- metadata +164 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'credentials'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'atmos'
|
4
|
+
|
5
|
+
|
6
|
+
class AtmosObjectCreateTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def initialize(id)
|
9
|
+
super(id)
|
10
|
+
@cleanup = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@s = Atmos::Test::Util.static_store
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
@cleanup.each do |obj|
|
20
|
+
obj.delete
|
21
|
+
end
|
22
|
+
$stdout.flush
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_create_empty_object
|
26
|
+
obj = @s.create
|
27
|
+
@cleanup.push(obj)
|
28
|
+
|
29
|
+
assert_not_nil(obj)
|
30
|
+
assert_equal({}, obj.metadata)
|
31
|
+
assert_equal({}, obj.listable_metadata)
|
32
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
33
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_create_object_with_bad_option
|
37
|
+
assert_raise Atmos::Exceptions::ArgumentException do
|
38
|
+
@cleanup.push(@s.create(:tag => ['testtag']))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_create_object_with_md_as_string
|
43
|
+
assert_raise Atmos::Exceptions::ArgumentException do
|
44
|
+
@cleanup.push(@s.create(:metadata => 'testtag'))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_create_object_with_md_as_array
|
49
|
+
assert_raise Atmos::Exceptions::ArgumentException do
|
50
|
+
@cleanup.push(@s.create(:metadata => ['testtag']))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_create_object_with_metadata
|
55
|
+
md = {'non' => 'listable', 'user' => 'metadata', 'a' => 'b'}
|
56
|
+
obj = @s.create(:metadata => md)
|
57
|
+
@cleanup.push(obj)
|
58
|
+
assert_not_nil(obj)
|
59
|
+
|
60
|
+
assert(obj.metadata.non_listable?)
|
61
|
+
assert(!obj.metadata.listable?)
|
62
|
+
assert_not_equal({}, obj.metadata)
|
63
|
+
assert_equal(md, obj.metadata)
|
64
|
+
|
65
|
+
assert_equal({}, obj.listable_metadata)
|
66
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
67
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_create_object_with_listable_metadata
|
71
|
+
lmd = {'x' => 'a', 'y' => 'b', 'z' => 'c'}
|
72
|
+
obj = @s.create(:listable_metadata => lmd)
|
73
|
+
@cleanup.push(obj)
|
74
|
+
assert_not_nil(obj)
|
75
|
+
assert_equal({}, obj.metadata)
|
76
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
77
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
78
|
+
|
79
|
+
assert_not_equal({}, obj.listable_metadata)
|
80
|
+
assert_equal(lmd, obj.listable_metadata)
|
81
|
+
assert(!obj.listable_metadata.non_listable?)
|
82
|
+
assert(obj.listable_metadata.listable?)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_create_object_with_mixed_metadata
|
86
|
+
md = {'non' => 'listable', 'user' => 'metadata', 'a' => 'b'}
|
87
|
+
lmd = {'x' => 'a', 'y' => 'b', 'z' => 'c'}
|
88
|
+
obj = @s.create(:metadata => md, :listable_metadata => lmd)
|
89
|
+
@cleanup.push(obj)
|
90
|
+
assert_not_nil(obj)
|
91
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
92
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
93
|
+
|
94
|
+
assert(!obj.listable_metadata.non_listable?)
|
95
|
+
assert(obj.listable_metadata.listable?)
|
96
|
+
assert_not_equal({}, obj.listable_metadata)
|
97
|
+
assert_equal(lmd, obj.listable_metadata)
|
98
|
+
|
99
|
+
assert(obj.metadata.non_listable?)
|
100
|
+
assert(!obj.metadata.listable?)
|
101
|
+
assert_not_equal({}, obj.metadata)
|
102
|
+
assert_equal(md, obj.metadata)
|
103
|
+
end
|
104
|
+
|
105
|
+
# def test_create_object_with_acl
|
106
|
+
# end
|
107
|
+
|
108
|
+
def test_create_object_with_data_as_string_with_length
|
109
|
+
data = "some stupid blob content"
|
110
|
+
obj = @s.create(:data => data, :length => data.length)
|
111
|
+
@cleanup.push(obj)
|
112
|
+
assert_equal(data, obj.data)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_create_object_with_data_as_string_without_length
|
116
|
+
data = "some stupid blob content"
|
117
|
+
obj = @s.create(:data => data)
|
118
|
+
@cleanup.push(obj)
|
119
|
+
assert_equal(data, obj.data)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_create_object_with_data_as_stream_with_length
|
123
|
+
contents = open(Atmos::Test::Util.small_text_filename).read
|
124
|
+
data = open(Atmos::Test::Util.small_text_filename)
|
125
|
+
obj = @s.create(:data => data, :length => data.stat.size)
|
126
|
+
@cleanup.push(obj)
|
127
|
+
assert_equal(contents, obj.data)
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_create_object_with_data_as_stream_without_length
|
131
|
+
file = open(Atmos::Test::Util.small_text_filename)
|
132
|
+
contents = file.read
|
133
|
+
file.close
|
134
|
+
data = open(Atmos::Test::Util.small_text_filename)
|
135
|
+
obj = @s.create(:data => data)
|
136
|
+
@cleanup.push(obj)
|
137
|
+
assert_equal(contents, obj.data)
|
138
|
+
end
|
139
|
+
|
140
|
+
def donttest_create_with_IO_as_data
|
141
|
+
@s = Atmos::Test::Util.static_store
|
142
|
+
@md = {'non' => 'listable', 'user' => 'metadata', 'a' => 'b'}
|
143
|
+
@lmd = {'x' => 'a', 'y' => 'b', 'z' => 'c'}
|
144
|
+
data = open(Atmos::Test::Util.small_binary_filename)
|
145
|
+
obj = @s.create(:metadata => @md,
|
146
|
+
:listable_metadata => @lmd,
|
147
|
+
:data => File.new(Atmos::Test::Util.small_binary_filename, "rb"))
|
148
|
+
@cleanup.push(obj)
|
149
|
+
@aoid = obj.aoid
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'credentials'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'atmos'
|
4
|
+
|
5
|
+
|
6
|
+
class AtmosObjectMiscTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def initialize(id)
|
9
|
+
super(id)
|
10
|
+
@cleanup = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@s = Atmos::Test::Util.static_store
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
@cleanup.each do |obj|
|
20
|
+
obj.delete
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def test_delete_existing_object
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_exists_on_existant_object
|
29
|
+
obj = @s.create()
|
30
|
+
@cleanup.push(obj)
|
31
|
+
assert(obj.exists?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_exists_on_non_existant_object
|
35
|
+
obj = @s.create()
|
36
|
+
obj.delete
|
37
|
+
assert(!obj.exists?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_delete_nonexisting_object
|
41
|
+
first = @s.create
|
42
|
+
assert_not_nil(first)
|
43
|
+
second = @s.get(first.aoid)
|
44
|
+
assert_not_nil(second)
|
45
|
+
first.delete
|
46
|
+
assert_raise Atmos::Exceptions::NoSuchObjectException do
|
47
|
+
second.delete
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_truncate_text_object
|
52
|
+
file = open(Atmos::Test::Util.small_text_filename)
|
53
|
+
contents = file.read
|
54
|
+
file.close
|
55
|
+
data = open(Atmos::Test::Util.small_text_filename)
|
56
|
+
obj = @s.create(:data => data)
|
57
|
+
@cleanup.push(obj)
|
58
|
+
assert_equal(contents, obj.data)
|
59
|
+
|
60
|
+
obj.truncate
|
61
|
+
assert_equal("", obj.data)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def test_truncate_binary_object
|
66
|
+
file = open(Atmos::Test::Util.small_binary_filename)
|
67
|
+
contents = file.read
|
68
|
+
file.close
|
69
|
+
data = open(Atmos::Test::Util.small_binary_filename)
|
70
|
+
obj = @s.create(:data => data)
|
71
|
+
@cleanup.push(obj)
|
72
|
+
|
73
|
+
# assert_equal(contents, obj.data)
|
74
|
+
|
75
|
+
obj.truncate
|
76
|
+
assert_equal("", obj.data)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'credentials'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'atmos'
|
4
|
+
|
5
|
+
|
6
|
+
class AtmosObjectCreateTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def initialize(id)
|
9
|
+
super(id)
|
10
|
+
@cleanup = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@s = Atmos::Test::Util.static_store
|
15
|
+
@md = {'non' => 'listable', 'user' => 'metadata', 'a' => 'b'}
|
16
|
+
@lmd = {'x' => 'a', 'y' => 'b', 'z' => 'c'}
|
17
|
+
data = open(Atmos::Test::Util.small_binary_filename)
|
18
|
+
obj = @s.create(:metadata => @md,
|
19
|
+
:listable_metadata => @lmd,
|
20
|
+
:data => File.new(Atmos::Test::Util.small_binary_filename, "rb"))
|
21
|
+
@cleanup.push(obj)
|
22
|
+
@aoid = obj.aoid
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
@cleanup.each do |obj|
|
28
|
+
obj.delete
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def test_read_partial_data_as_stream
|
34
|
+
# something is very strange here
|
35
|
+
# the object shouldn't end up empty, but it does
|
36
|
+
# it doesn't end up empty when this file is run
|
37
|
+
# as a standalone test
|
38
|
+
file = open(Atmos::Test::Util.small_binary_filename)
|
39
|
+
contents = file.read
|
40
|
+
obj = @s.get(@aoid)
|
41
|
+
obj.update(contents)
|
42
|
+
assert_equal(contents, obj.data)
|
43
|
+
|
44
|
+
f = File.new(Atmos::Test::Util.small_binary_filename, "rb")
|
45
|
+
contents = f.read(61)
|
46
|
+
data = obj.data(0...60)
|
47
|
+
|
48
|
+
readin = ""
|
49
|
+
obj.data_as_stream(0...60) do |chunk|
|
50
|
+
readin.concat(chunk)
|
51
|
+
end
|
52
|
+
|
53
|
+
assert_equal(61, readin.length)
|
54
|
+
assert_equal(contents.length, readin.length)
|
55
|
+
assert_equal(contents, readin)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_read_data_as_stream
|
59
|
+
|
60
|
+
# something is very strange here
|
61
|
+
# the object shouldn't end up empty, but it does
|
62
|
+
# it doesn't end up empty when this file is run
|
63
|
+
# as a standalone test
|
64
|
+
file = open(Atmos::Test::Util.small_binary_filename)
|
65
|
+
contents = file.read
|
66
|
+
obj = @s.get(@aoid)
|
67
|
+
obj.update(contents)
|
68
|
+
assert_equal(contents, obj.data)
|
69
|
+
|
70
|
+
readin = ""
|
71
|
+
obj.data_as_stream do |chunk|
|
72
|
+
readin.concat(chunk)
|
73
|
+
end
|
74
|
+
|
75
|
+
assert_equal(contents, readin)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_read_partial_data_as_string
|
79
|
+
|
80
|
+
# something is very strange here
|
81
|
+
# the object shouldn't end up empty, but it does
|
82
|
+
# it doesn't end up empty when this file is run
|
83
|
+
# as a standalone test
|
84
|
+
file = open(Atmos::Test::Util.small_binary_filename)
|
85
|
+
contents = file.read
|
86
|
+
obj = @s.get(@aoid)
|
87
|
+
obj.update(contents)
|
88
|
+
assert_equal(contents, obj.data)
|
89
|
+
|
90
|
+
|
91
|
+
f = File.new(Atmos::Test::Util.small_binary_filename, "rb")
|
92
|
+
contents = f.read(61)
|
93
|
+
data = obj.data(0...60)
|
94
|
+
len = data.length
|
95
|
+
assert_equal(61, obj.data(0...60).length)
|
96
|
+
assert_equal(contents, obj.data(0...60))
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_read_data_as_string
|
100
|
+
# something is very strange here
|
101
|
+
# the object shouldn't end up empty, but it does
|
102
|
+
# it doesn't end up empty when this file is run
|
103
|
+
# as a standalone test
|
104
|
+
file = open(Atmos::Test::Util.small_binary_filename)
|
105
|
+
contents = file.read
|
106
|
+
obj = @s.get(@aoid)
|
107
|
+
obj.update(contents)
|
108
|
+
assert_equal(contents, obj.data)
|
109
|
+
|
110
|
+
assert_equal(contents, obj.data)
|
111
|
+
assert(obj.data.length > 0)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'credentials'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'atmos'
|
4
|
+
|
5
|
+
|
6
|
+
class AtmosObjectCreateTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def initialize(id)
|
9
|
+
super(id)
|
10
|
+
@cleanup = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@s = Atmos::Test::Util.static_store
|
15
|
+
@obj = @s.create
|
16
|
+
@cleanup.push(@obj)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def teardown
|
21
|
+
@cleanup.each do |obj|
|
22
|
+
obj.delete
|
23
|
+
end
|
24
|
+
$stdout.flush
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_object_ok
|
28
|
+
assert_not_nil(@obj)
|
29
|
+
assert_equal({}, @obj.metadata)
|
30
|
+
assert_equal({}, @obj.listable_metadata)
|
31
|
+
assert_equal({Atmos::Test::Util.user => :full}, @obj.user_acl)
|
32
|
+
assert_equal({Atmos::Test::Util.group => :none}, @obj.group_acl)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_update_empty_obj
|
36
|
+
data = "Hello World"
|
37
|
+
@obj.update(data)
|
38
|
+
assert_equal(data, @obj.data)
|
39
|
+
end
|
40
|
+
|
41
|
+
# def test_append_non_empty_object
|
42
|
+
# data = @obj.data
|
43
|
+
# @obj.append("!")
|
44
|
+
# assert_equal(data+"!", @obj.data)
|
45
|
+
# print "appended hello world object with !: #{@obj.data}\n"
|
46
|
+
# end
|
47
|
+
|
48
|
+
def test_update_object_in_place
|
49
|
+
data = "Hello World!"
|
50
|
+
@obj.update(data)
|
51
|
+
assert_equal(data, @obj.data)
|
52
|
+
|
53
|
+
range = 0..4
|
54
|
+
@obj.update("Mad ", range)
|
55
|
+
assert_equal("Mad World!", @obj.data)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/test/test_store.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'credentials'
|
3
|
+
require 'atmos'
|
4
|
+
|
5
|
+
|
6
|
+
class AtmosStoreConstructorTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def initialize( id )
|
9
|
+
super(id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def test_connect_without_parser
|
21
|
+
assert_raise Atmos::Exceptions::ArgumentException do
|
22
|
+
Atmos::Store.new(:uri => Atmos::Test::Util.url,
|
23
|
+
:uid => Atmos::Test::Util.user,
|
24
|
+
:secret => Atmos::Test::Util.secret)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_connect_with_parser
|
29
|
+
Atmos::Parser::parser = Atmos::Parser::NOKOGIRI
|
30
|
+
s = Atmos::Store.new(:url => Atmos::Test::Util.url,
|
31
|
+
:uid => Atmos::Test::Util.uid,
|
32
|
+
:secret => Atmos::Test::Util.secret)
|
33
|
+
assert_not_nil(s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_redirect
|
37
|
+
Atmos::Parser::parser = Atmos::Parser::NOKOGIRI
|
38
|
+
if (!Atmos::Test::Util.redirect_url.nil?)
|
39
|
+
assert_raise Atmos::Exceptions::NotImplementedException do
|
40
|
+
Atmos::Store.new(:url => Atmos::Test::Util.redirect_url,
|
41
|
+
:uid => Atmos::Test::Util.uid,
|
42
|
+
:secret => Atmos::Test::Util.secret)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_bad_option
|
48
|
+
Atmos::Parser::parser = Atmos::Parser::NOKOGIRI
|
49
|
+
assert_raise Atmos::Exceptions::ArgumentException do
|
50
|
+
Atmos::Store.new(:uri => Atmos::Test::Util.url,
|
51
|
+
:uid => Atmos::Test::Util.user,
|
52
|
+
:secret => Atmos::Test::Util.secret,
|
53
|
+
:foo => true)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_connect_to_bad_server_version
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class AtmosStoreTest < Test::Unit::TestCase
|
64
|
+
|
65
|
+
def initialize( id )
|
66
|
+
super(id)
|
67
|
+
@cleanup = []
|
68
|
+
end
|
69
|
+
|
70
|
+
def setup
|
71
|
+
@s = Atmos::Test::Util.static_store;
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def teardown
|
76
|
+
@cleanup.each do |obj|
|
77
|
+
obj.delete
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def test_get_atmos_version
|
83
|
+
rv = @s.server_version
|
84
|
+
assert_not_nil(rv)
|
85
|
+
assert_equal(String, rv.class)
|
86
|
+
assert_equal(Atmos::Test::Util.version, rv) if (!Atmos::Test::Util.version.nil?)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def dont_test_iterate_through_listable_tags
|
91
|
+
i = 0;
|
92
|
+
ctag = "ruby-atmos-curseword#{$$}"
|
93
|
+
obj = @s.create(:listable_metadata => {ctag => true})
|
94
|
+
@cleanup.push(obj)
|
95
|
+
|
96
|
+
@s.each_listable_tag do |tag|
|
97
|
+
i += 1
|
98
|
+
end
|
99
|
+
|
100
|
+
assert(i > 0)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def test_iterate_through_listable_tags_with_token
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
def test_iterate_through_objects_with_tag
|
110
|
+
srand()
|
111
|
+
tag = "ruby-atmos-curseword-#{rand()}"
|
112
|
+
|
113
|
+
for i in (1...10)
|
114
|
+
obj = @s.create(:listable_metadata => {tag => true})
|
115
|
+
end
|
116
|
+
|
117
|
+
i = 0
|
118
|
+
@s.each_object_with_listable_tag(tag) do |obj|
|
119
|
+
@cleanup.push(obj)
|
120
|
+
i += 1
|
121
|
+
end
|
122
|
+
assert_equal(9, i)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|