protopuffs 0.3.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.
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class MessageFieldTest < Test::Unit::TestCase
6
+
7
+ context "creating a MessageField" do
8
+ should "not allow you to instantiate MessageField directly" do
9
+ assert_raises RuntimeError do
10
+ Protopuffs::MessageField.new "_", "_", "_", "_"
11
+ end
12
+ end
13
+
14
+ should "complain if an invalid modifier is specified" do
15
+ ["optional", "required", "repeated"].each do |modifier|
16
+ assert_nothing_raised {Protopuffs::Int32.new(modifier, 'identitifer', 1)}
17
+ end
18
+ assert_raises(ArgumentError) {Protopuffs::Int32.new("invalid_modifier", "identifier", 1)}
19
+ end
20
+
21
+ should "instantiate the right class, given a type string" do
22
+ [[ "int32", Protopuffs::Int32],
23
+ [ "int64", Protopuffs::Int64],
24
+ [ "uint32", Protopuffs::UInt32],
25
+ [ "uint64", Protopuffs::UInt64],
26
+ [ "bool", Protopuffs::Bool],
27
+ [ "double", Protopuffs::Double],
28
+ [ "fixed64", Protopuffs::Fixed64],
29
+ [ "string", Protopuffs::String],
30
+ [ "bytes", Protopuffs::Bytes],
31
+ [ "float", Protopuffs::Float],
32
+ [ "fixed32", Protopuffs::Fixed32],
33
+ [ "sfixed32", Protopuffs::SFixed32],
34
+ [ "embedded", Protopuffs::Embedded],
35
+ ].each do |type, klass|
36
+ assert_kind_of klass, Protopuffs::MessageField.factory(type, "optional", "a_string", 1, 2)
37
+ end
38
+ end
39
+
40
+ should "set a string's default to '' when a default isn't specified" do
41
+ field = Protopuffs::String.new("optional", "name", 1)
42
+ assert_equal "", field.default
43
+ end
44
+
45
+ should "set a string's default to '' when a default is specified as nil" do
46
+ field = Protopuffs::String.new("optional", "name", 1, nil)
47
+ assert_equal "", field.default
48
+ end
49
+
50
+ should "set a numeric's default to 0 when a default isn't specified or is specified as nil" do
51
+ numeric_types = [Protopuffs::Double, Protopuffs::Float,
52
+ Protopuffs::Int32, Protopuffs::Int64,
53
+ Protopuffs::UInt32, Protopuffs::UInt64,
54
+ Protopuffs::Fixed32, Protopuffs::Fixed64,
55
+ Protopuffs::SFixed32]
56
+ numeric_types.each do |type|
57
+ assert_equal 0, type.new("optional", "number", 1).default
58
+ assert_equal 0, type.new("optional", "number", 1, nil).default
59
+ end
60
+ end
61
+
62
+ should "set a bool's default to false when a default isn't specified" do
63
+ field = Protopuffs::Bool.new("optional", "opt_in", 1)
64
+ assert_same false, field.default
65
+ end
66
+
67
+ should "set a bool's default to false when a default is specified as nil" do
68
+ field = Protopuffs::Bool.new("optional", "opt_in", 1, nil)
69
+ assert_same false, field.default
70
+ end
71
+
72
+ should "set the default to 'Matz' when that default is specified" do
73
+ field = Protopuffs::String.new("optional", "name", 1, "Matz")
74
+ assert_equal "Matz", field.default
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,208 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class ParseTreeTest < Test::Unit::TestCase
6
+
7
+ context "a protocol buffer descriptor" do
8
+ setup do
9
+ @parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
10
+ end
11
+
12
+ context "with an empty Person message" do
13
+ setup do
14
+ @proto = @parser.parse("message Person {}")
15
+ end
16
+
17
+ should "have one message" do
18
+ assert_equal 1, @proto.messages.size
19
+ end
20
+
21
+ should "have a message named Person" do
22
+ assert_equal "Person", @proto.messages.first.name
23
+ end
24
+
25
+ should "have an empty body" do
26
+ assert @proto.messages.first.body.empty?
27
+ end
28
+ end
29
+
30
+ context "with two empty messages Apple and Orange" do
31
+ setup do
32
+ @descriptor = <<-proto
33
+ message Apple {}
34
+ message Orange {}
35
+ proto
36
+ end
37
+
38
+ should "have two messages" do
39
+ proto = @parser.parse(@descriptor)
40
+ assert_equal 2, proto.messages.size
41
+ end
42
+
43
+ should "have messages named Apple and Orange" do
44
+ proto = @parser.parse(@descriptor)
45
+ assert_equal %w(Apple Orange), proto.messages.map { |m| m.name }.sort
46
+ end
47
+
48
+ should "have messages with empty bodes" do
49
+ proto = @parser.parse(@descriptor)
50
+ assert proto.messages.all? { |m| m.body.empty? }
51
+ end
52
+ end
53
+
54
+ context "a proto with a Person message including a name field" do
55
+ setup do
56
+ @descriptor = <<-proto
57
+ message Person {
58
+ required string name = 1;
59
+ }
60
+ proto
61
+ end
62
+
63
+ should "have one message named person" do
64
+ proto = @parser.parse(@descriptor)
65
+ assert_equal 1, proto.messages.size
66
+ assert_equal "Person", proto.messages.first.name
67
+ end
68
+
69
+ should "have one required string field called name with tag 1" do
70
+ proto = @parser.parse(@descriptor)
71
+ fields = proto.messages.first.body.fields
72
+ assert_equal 1, fields.size
73
+ assert_equal "required", fields.first.modifier.text_value
74
+ assert_equal "string", fields.first.type.text_value
75
+ assert_equal "name", fields.first.identifier.text_value
76
+ assert_equal "1", fields.first.integer.text_value
77
+ end
78
+ end
79
+
80
+ context "with a Person message including three fields" do
81
+ setup do
82
+ @descriptor = <<-proto
83
+ message Person {
84
+ required string name = 1;
85
+ required int32 id = 2;
86
+ optional string email = 3;
87
+ }
88
+ proto
89
+ end
90
+
91
+ should "have one message named person" do
92
+ proto = @parser.parse(@descriptor)
93
+ assert_equal 1, proto.messages.size
94
+ assert_equal "Person", proto.messages.first.name
95
+ end
96
+
97
+ should "have three fields with correct components" do
98
+ proto = @parser.parse(@descriptor)
99
+ fields = proto.messages.first.body.fields
100
+ assert_equal 3, fields.size
101
+ actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer].map { |el| el.text_value } }
102
+ expected = [ %w(required string name 1),
103
+ %w(required int32 id 2),
104
+ %w(optional string email 3) ]
105
+ assert_equal expected, actual
106
+ end
107
+ end
108
+
109
+ context "with a Person message including two fields with defaults and one without" do
110
+ setup do
111
+ @proto = @parser.parse(<<-proto)
112
+ message Person {
113
+ required string name = 1;
114
+ optional string language = 2 [default = "en"];
115
+ optional int32 account_code = 3 [default = 0];
116
+ }
117
+ proto
118
+ end
119
+
120
+ should "have one message named person" do
121
+ assert_equal 1, @proto.messages.size
122
+ assert_equal "Person", @proto.messages.first.name
123
+ end
124
+
125
+ should "have three fields with correct components" do
126
+ fields = @proto.messages.first.body.fields
127
+ assert_equal 3, fields.size
128
+ actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer, f.default] }
129
+ actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
130
+ expected = [ ["required", "string", "name", "1", nil],
131
+ ["optional", "string", "language", "2", "en"],
132
+ ["optional", "int32", "account_code", "3", 0] ]
133
+ assert_equal expected, actual
134
+ end
135
+ end
136
+
137
+ context "with a message including lots of comments" do
138
+ setup do
139
+ @proto = @parser.parse(<<-proto)
140
+ // test
141
+ //test
142
+ message Person { //test // test
143
+ // test
144
+ required string name = 1; // test
145
+ // optional string language = 2 [default = "en"];
146
+ optional int32 account_code = 3 [default = 0]; // test
147
+ // test
148
+ } // test
149
+ // test
150
+ //test
151
+ proto
152
+ end
153
+
154
+ should "parse" do
155
+ assert_not_nil @proto
156
+ end
157
+
158
+ should "have uncommented fields with correct components" do
159
+ fields = @proto.messages.first.body.fields
160
+ assert_equal 2, fields.size
161
+ actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer, f.default] }
162
+ actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
163
+ expected = [ ["required", "string", "name", "1", nil],
164
+ ["optional", "int32", "account_code", "3", 0] ]
165
+ assert_equal expected, actual
166
+ end
167
+ end
168
+
169
+
170
+ context "with a message including a user-typed field" do
171
+ setup do
172
+ @proto = @parser.parse(<<-proto)
173
+ message Person {
174
+ required string name = 1;
175
+ repeated Address addresses = 2;
176
+ }
177
+ proto
178
+ end
179
+
180
+ should "parse" do
181
+ assert_not_nil @proto
182
+ end
183
+
184
+ should "have two fields with correct components" do
185
+ fields = @proto.messages.first.body.fields
186
+ assert_equal 2, fields.size
187
+ actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer] }
188
+ actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
189
+ expected = [ ["required", "string", "name", "1"],
190
+ ["repeated", "Address", "addresses", "2"] ]
191
+ assert_equal expected, actual
192
+ end
193
+ end
194
+
195
+
196
+ should "raise a ParseError when parsing a message with a syntax error" do
197
+ assert_raises Protopuffs::ParseError do
198
+ @parser.parse(<<-proto)
199
+ message Person {
200
+ required name = 1
201
+ }
202
+ proto
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class ProtopuffsTest < Test::Unit::TestCase
6
+
7
+ context ".proto_load_path accessors" do
8
+ setup { Protopuffs.proto_load_path = [] }
9
+
10
+ should "have an accessor for an array of load paths for .proto files" do
11
+ Protopuffs.proto_load_path << "proto_files" << "other_proto_files"
12
+ assert_equal ["proto_files", "other_proto_files"], Protopuffs.proto_load_path
13
+ end
14
+
15
+ should "have a mutator for directly assigning the load paths" do
16
+ Protopuffs.proto_load_path = ["my_proto_files"]
17
+ assert_equal ["my_proto_files"], Protopuffs.proto_load_path
18
+ end
19
+ end
20
+
21
+ should "have a ParseError class" do
22
+ Protopuffs::ParseError
23
+ end
24
+
25
+
26
+ context ".load_message_classes when a descriptor is in the load path with a Person message" do
27
+ setup { Protopuffs.proto_load_path = ["#{File.dirname(__FILE__)}/fixtures/proto"] }
28
+
29
+ should "create a Person message class with correct accessors" do
30
+ Protopuffs.load_message_classes
31
+ p = Protopuffs::Message::Person.new
32
+ p.name = "Chris"
33
+ p.id = 42
34
+ p.email = "chris@kampers.net"
35
+ assert_equal ["Chris", 42, "chris@kampers.net"], [p.name, p.id, p.email]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+
6
+ gem "polyglot", "=0.2.9"
7
+ gem "treetop", "=1.4.2"
8
+
9
+ gem "thoughtbot-shoulda", "=2.10.2"
10
+ require 'shoulda'
11
+
12
+ gem "mocha", "=0.9.8"
13
+ require 'mocha'
14
+
15
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
16
+ require 'protopuffs'
17
+
18
+ class Test::Unit::TestCase
19
+
20
+ # helper for debugging
21
+ def print_bytes(string)
22
+ puts
23
+ string.each_byte do |byte|
24
+ printf("%1$08b (%1$02X)\n", byte)
25
+ end
26
+ end
27
+
28
+ # helper for generating test names
29
+ def self.inspect_bytes(bytes_array)
30
+ "[#{bytes_array.map { |b| sprintf("%02X", b) }.join(' ')}]"
31
+ end
32
+
33
+
34
+ def self.should_encode_wire_format_from_fields(expected_bytes, actual_fields)
35
+ should "encode the fields #{actual_fields.inspect} to the byte string #{inspect_bytes(expected_bytes)}" do
36
+ actual_fields.each_pair do |name, value|
37
+ value = value.call if value.respond_to?(:call)
38
+ @message.send("#{name}=", value)
39
+ end
40
+ actual_bytes = @message.to_wire_format.unpack('C*')
41
+ assert_equal expected_bytes, actual_bytes
42
+ end
43
+ end
44
+
45
+ def self.should_decode_wire_format_to_fields(actual_bytes, expected_fields)
46
+ should "decode the byte string #{inspect_bytes(actual_bytes)} to the fields #{expected_fields.inspect}" do
47
+ buffer = StringIO.new(actual_bytes.pack('C*'))
48
+ buffer.set_encoding("BINARY") if buffer.respond_to?(:set_encoding)
49
+ @message.from_wire_format(buffer)
50
+ actual_fields = @message.class.fields.inject({}) { |hash, field|
51
+ hash[field.identifier.to_sym] = @message.send(field.identifier)
52
+ hash
53
+ }
54
+
55
+ expected_fields.each_pair do |key, expected_value|
56
+ expected_value = expected_value.call if expected_value.respond_to?(:call)
57
+ if expected_value.is_a?(Float)
58
+ assert_in_delta(expected_value, actual_fields[key], Float::EPSILON)
59
+ else
60
+ assert_equal expected_value, actual_fields[key]
61
+ end
62
+ end
63
+
64
+ assert_equal expected_fields.size, actual_fields.size
65
+ end
66
+ end
67
+
68
+ def self.should_encode_and_decode_wire_format_and_fields(bytes, fields)
69
+ self.should_encode_wire_format_from_fields(bytes, fields)
70
+ self.should_decode_wire_format_to_fields(bytes, fields)
71
+ end
72
+
73
+ def self.should_losslessly_encode_and_decode_a_random_sample(fields)
74
+ raise ArgumentError if fields.size > 1
75
+ name, range = fields.shift
76
+ should "get the same value back after encoding and decoding a random sample of the values #{range.inspect} for field #{name.inspect}" do
77
+ size = range.last - range.first
78
+ size -= 1 if range.exclude_end?
79
+ values_to_test = [range.first, range.first + size]
80
+ 250.times { values_to_test << range.first + rand(size) }
81
+ values_to_test.each do |value|
82
+ @message.send("#{name}=", value)
83
+ @message.from_wire_format(@message.to_wire_format)
84
+ assert_equal value, @message.send(name)
85
+ end
86
+ end
87
+ end
88
+
89
+ end
90
+
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class TextFormatTest < Test::Unit::TestCase
6
+
7
+ context "a message called Test1 with one int32 field" do
8
+ # from http://code.google.com/apis/protocolbuffers/docs/overview.html#whynotxml
9
+ setup do
10
+ fields = [Protopuffs::String.new("required", "name", 1),
11
+ Protopuffs::String.new("required", "email", 2)]
12
+ Protopuffs::Message::Base.define_message_class("Person", fields)
13
+ @message = Protopuffs::Message::Person.new
14
+ @message.name = "John Doe"
15
+ @message.email = "jdoe@example.com"
16
+ end
17
+
18
+ should "return the correct text format from #inspect" do
19
+ assert_equal %Q(person {\n name: "John Doe"\n email: "jdoe@example.com"\n}), @message.inspect
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,293 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class WireFormatTest < Test::Unit::TestCase
6
+
7
+ context "a message with one int32 field tagged #1" do
8
+ # from http://code.google.com/apis/protocolbuffers/docs/encoding.html#simple
9
+ setup do
10
+ fields = [Protopuffs::Int32.new("required", "a", 1)]
11
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
12
+ @message = Protopuffs::Message::Test1.new
13
+ end
14
+
15
+ should_encode_wire_format_from_fields [0x08, 0x96, 0x01], :a => 150
16
+ should_decode_wire_format_to_fields [0x08, 0x96, 0x01], :a => 150
17
+
18
+ # should ignore unknown fields: this message also has an int32 tagged #2 with value 157,372
19
+ should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09], :a => 150
20
+
21
+ should "return itself from #from_wire_format" do
22
+ wire_message = StringIO.new([0x08, 0x96, 0x01].pack('C*'))
23
+ assert_same @message, @message.from_wire_format(wire_message)
24
+ end
25
+
26
+ should "accept a string as an argument to #from_wire_format" do
27
+ wire_message = [0x08, 0x96, 0x01].pack('C*')
28
+ @message.from_wire_format(wire_message)
29
+ assert_equal 150, @message.a
30
+ end
31
+ end
32
+
33
+
34
+ context "a message with two int32 fields tagged #1 and #2" do
35
+ setup do
36
+ fields = [Protopuffs::Int32.new("required", "a", 1),
37
+ Protopuffs::Int32.new("required", "b", 2)]
38
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
39
+ @message = Protopuffs::Message::Test1.new
40
+ end
41
+
42
+ should_encode_wire_format_from_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09],
43
+ :a => 150, :b => 157_372
44
+ should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09],
45
+ :a => 150, :b => 157_372
46
+ end
47
+
48
+
49
+ context "a message with one int64 field tagged #1" do
50
+ setup do
51
+ fields = [Protopuffs::Int64.new("required", "a", 1)]
52
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
53
+ @message = Protopuffs::Message::Test1.new
54
+ end
55
+
56
+ should_encode_wire_format_from_fields [0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02],
57
+ :a => 2**50
58
+ should_decode_wire_format_to_fields [0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02],
59
+ :a => 2**50
60
+ end
61
+
62
+
63
+ context "a message with one uint32 field tagged #1 and one uint64 field tagged #2" do
64
+ setup do
65
+ fields = [Protopuffs::UInt32.new("required", "a", 1),
66
+ Protopuffs::UInt64.new("required", "b", 2)]
67
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
68
+ @message = Protopuffs::Message::Test1.new
69
+ end
70
+
71
+ should_encode_wire_format_from_fields [0x08, 0x90, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20],
72
+ :a => 912, :b => 2**54
73
+ should_decode_wire_format_to_fields [0x08, 0x90, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20],
74
+ :a => 912, :b => 2**54
75
+ end
76
+
77
+
78
+ context "a message with two bool fields tagged #1 and #2" do
79
+ setup do
80
+ fields = [Protopuffs::Bool.new("required", "a", 1),
81
+ Protopuffs::Bool.new("required", "b", 2)]
82
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
83
+ @message = Protopuffs::Message::Test1.new
84
+ end
85
+
86
+ should_encode_wire_format_from_fields [0x08, 0x00, 0x10, 0x01],
87
+ :a => false, :b => true
88
+ should_decode_wire_format_to_fields [0x08, 0x00, 0x10, 0x01],
89
+ :a => false, :b => true
90
+ end
91
+
92
+
93
+ context "a message with one string field tagged #2" do
94
+ setup do
95
+ # from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
96
+ fields = [Protopuffs::String.new("required", "b", 2)]
97
+ Protopuffs::Message::Base.define_message_class("Test2", fields)
98
+ @message = Protopuffs::Message::Test2.new
99
+ end
100
+
101
+ should_encode_wire_format_from_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
102
+ :b => "testing"
103
+ should_decode_wire_format_to_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
104
+ :b => "testing"
105
+
106
+ should_encode_wire_format_from_fields [0x12, 0x01, 0x32], :b => 2
107
+ should_decode_wire_format_to_fields [0x12, 0x01, 0x32], :b => "2"
108
+
109
+ should_encode_wire_format_from_fields [0x12, 0x0C, 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x8F],
110
+ :b => "こにちわ"
111
+ should_decode_wire_format_to_fields [0x12, 0x0C, 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x8F],
112
+ :b => "こにちわ"
113
+
114
+ should_encode_wire_format_from_fields [0x12, 0x05, 0xD2, 0x90, 0x41, 0xC3, 0x9C],
115
+ :b => "ҐAÜ"
116
+ should_decode_wire_format_to_fields [0x12, 0x05, 0xD2, 0x90, 0x41, 0xC3, 0x9C],
117
+ :b => "ҐAÜ"
118
+ end
119
+
120
+
121
+ context "a message with a bytes field tagged #1" do
122
+ setup do
123
+ fields = [Protopuffs::Bytes.new("required", "a", 1)]
124
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
125
+ @message = Protopuffs::Message::Test1.new
126
+ end
127
+
128
+ should_encode_wire_format_from_fields [0x0A, 0x04, 0xDE, 0xCA, 0xFB, 0xAD],
129
+ :a => [0xDE, 0xCA, 0xFB, 0xAD].pack('C*')
130
+ should_decode_wire_format_to_fields [0x0A, 0x04, 0xDE, 0xCA, 0xFB, 0xAD],
131
+ :a => [0xDE, 0xCA, 0xFB, 0xAD].pack('C*')
132
+ end
133
+
134
+
135
+ context "a message with one float field tagged #1" do
136
+ setup do
137
+ fields = [Protopuffs::Float.new("required", "a", 1)]
138
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
139
+ @message = Protopuffs::Message::Test1.new
140
+ end
141
+
142
+ # 1.61803 gives you repeating binary digits when encoded, so the number
143
+ # you get from decoding is different (within Float::EPSILON)
144
+ should_encode_wire_format_from_fields [0x0D, 0x9B, 0x1B, 0xCF, 0x3F],
145
+ :a => 1.61803
146
+ should_decode_wire_format_to_fields [0x0D, 0x9B, 0x1B, 0xCF, 0x3F],
147
+ :a => 1.6180299520492554
148
+ end
149
+
150
+
151
+ context "a message with one double field tagged #1" do
152
+ setup do
153
+ fields = [Protopuffs::Double.new("required", "a", 1)]
154
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
155
+ @message = Protopuffs::Message::Test1.new
156
+ end
157
+
158
+ # 64-bit doubles have enough precision to encode/decode 1.61803,
159
+ # unlike the 32-bit floats above
160
+ should_encode_wire_format_from_fields [0x09, 0x6C, 0x26, 0xDF, 0x6C, 0x73, 0xE3, 0xF9, 0x3F],
161
+ :a => 1.61803
162
+ should_decode_wire_format_to_fields [0x09, 0x6C, 0x26, 0xDF, 0x6C, 0x73, 0xE3, 0xF9, 0x3F],
163
+ :a => 1.61803
164
+ end
165
+
166
+
167
+ context "a message with one fixed64 field tagged #1" do
168
+ setup do
169
+ fields = [Protopuffs::Fixed64.new("required", "a", 1)]
170
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
171
+ @message = Protopuffs::Message::Test1.new
172
+ end
173
+
174
+ should_encode_wire_format_from_fields [0x09, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F],
175
+ :a => 2**62 - 15
176
+ should_decode_wire_format_to_fields [0x09, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F],
177
+ :a => 2**62 - 15
178
+
179
+ end
180
+
181
+
182
+ context "a message with one fixed32 field tagged #1" do
183
+ setup do
184
+ fields = [Protopuffs::Fixed32.new("required", "a", 1)]
185
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
186
+ @message = Protopuffs::Message::Test1.new
187
+ end
188
+
189
+ should_encode_wire_format_from_fields [0x0D, 0xFE, 0xFF, 0xFF, 0xFF],
190
+ :a => 2**32 - 2
191
+ should_decode_wire_format_to_fields [0x0D, 0xFE, 0xFF, 0xFF, 0xFF],
192
+ :a => 2**32 - 2
193
+ end
194
+
195
+
196
+ context "a message with one sfixed32 field tagged #1" do
197
+ setup do
198
+ fields = [Protopuffs::SFixed32.new("required", "a", 1)]
199
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
200
+ @message = Protopuffs::Message::Test1.new
201
+ end
202
+
203
+ should_encode_and_decode_wire_format_and_fields [0x0D, 0x05, 0x00, 0x00, 0x80],
204
+ :a => -2**31 + 5
205
+ should_encode_and_decode_wire_format_and_fields [0x0D, 0x05, 0x80, 0x00, 0x00],
206
+ :a => 2**15 + 5
207
+ should_losslessly_encode_and_decode_a_random_sample :a => -2**31...2**31
208
+ end
209
+
210
+
211
+ context "a message with one repeating int32 field tagged #1" do
212
+ setup do
213
+ fields = [Protopuffs::Int32.new("repeated", "a", 1)]
214
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
215
+ @message = Protopuffs::Message::Test1.new
216
+ end
217
+
218
+ should_encode_wire_format_from_fields [0x08, 0x96, 0x01, 0x08, 0xBC, 0xCD, 0x09, 0x08, 0x3D],
219
+ :a => [150, 157_372, 61]
220
+ should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x08, 0xBC, 0xCD, 0x09, 0x08, 0x3D],
221
+ :a => [150, 157_372, 61]
222
+ end
223
+
224
+
225
+ context "a message with one embedded-message field Test1 tagged #3 (where Test1 has an int32 field tagged #1)" do
226
+ # from http://code.google.com/apis/protocolbuffers/docs/encoding.html#embedded
227
+ setup do
228
+ test1_fields = [Protopuffs::Int32.new("required", "a", 1)]
229
+ Protopuffs::Message::Base.define_message_class("Test1", test1_fields)
230
+
231
+ test3_fields = [Protopuffs::Embedded.new("Test1", "required", "c", 3)]
232
+ Protopuffs::Message::Base.define_message_class("Test3", test3_fields)
233
+ @message = Protopuffs::Message::Test3.new
234
+ end
235
+
236
+ should_encode_wire_format_from_fields [0x1A, 0x03, 0x08, 0x96, 0x01],
237
+ :c => lambda { msg = Protopuffs::Message::Test1.new; msg.a = 150; msg }
238
+ should_decode_wire_format_to_fields [0x1A, 0x03, 0x08, 0x96, 0x01],
239
+ :c => lambda { msg = Protopuffs::Message::Test1.new; msg.a = 150; msg }
240
+ end
241
+
242
+
243
+ context "a message with two int32 fields tagged #1 (optional, default=150) and #2 (required)" do
244
+ setup do
245
+ fields = [Protopuffs::Int32.new("optional", "a", 1, 150),
246
+ Protopuffs::Int32.new("required", "b", 2)]
247
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
248
+ @message = Protopuffs::Message::Test1.new
249
+ end
250
+
251
+ should_encode_wire_format_from_fields [0x10, 0xBC, 0xCD, 0x09],
252
+ :b => 157_372
253
+ should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09],
254
+ :a => 150, :b => 157_372
255
+ end
256
+
257
+
258
+ context "a message with two int32 fields tagged #1 (optional, no default) and #2 (required)" do
259
+ setup do
260
+ fields = [Protopuffs::Int32.new("optional", "a", 1),
261
+ Protopuffs::Int32.new("required", "b", 2)]
262
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
263
+ @message = Protopuffs::Message::Test1.new
264
+ end
265
+
266
+ should_encode_wire_format_from_fields [0x10, 0xBC, 0xCD, 0x09],
267
+ :b => 157_372
268
+ should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09],
269
+ :a => 0, :b => 157_372
270
+ end
271
+
272
+
273
+ context "a message with two int32 fields tagged #1 and #2, defined with #2 first" do
274
+ setup do
275
+ fields = [Protopuffs::Int32.new("required", "b", 2),
276
+ Protopuffs::Int32.new("required", "a", 1)]
277
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
278
+ @message = Protopuffs::Message::Test1.new
279
+ end
280
+
281
+ # should always encode with fields ordered by tag number, to take
282
+ # advantage of decoders that optimize for this case
283
+ should_encode_wire_format_from_fields [0x08, 0x14, 0x10, 0xBC, 0xCD, 0x09],
284
+ :a => 20, :b => 157_372
285
+
286
+ # the decoder should still support any field order, though
287
+ should_decode_wire_format_to_fields [0x08, 0x14, 0x10, 0xBC, 0xCD, 0x09],
288
+ :a => 20, :b => 157_372
289
+ should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09, 0x08, 0x14],
290
+ :a => 20, :b => 157_372
291
+ end
292
+
293
+ end