chrisk-protopuffs 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,3 +1,74 @@
1
1
  = Protopuffs!
2
2
 
3
- A new implementation of Protocol Buffers in Ruby.
3
+ A new implementation of Protocol Buffers in Ruby.
4
+
5
+ If you're not familiar with Protocol Buffers, start with Google's homepage:
6
+ http://code.google.com/apis/protocolbuffers
7
+
8
+ Protocol buffers are Google's language-neutral, platform-neutral, extensible
9
+ mechanism for serializing structured data -- think XML, but smaller, faster,
10
+ and simpler.
11
+
12
+ == Installation
13
+
14
+ Rubyforge is cuckoo for protopuffs.
15
+
16
+ sudo gem install protopuffs
17
+
18
+ == Usage
19
+
20
+ Start with a +proto+ file, say, <tt>proto/animals.proto</tt>:
21
+
22
+ message Bird {
23
+ required string name = 1;
24
+ optional string species = 2;
25
+ }
26
+
27
+ First, require Protopuffs and tell it where your +proto+ files are:
28
+
29
+ require 'protopuffs'
30
+ Protopuffs.proto_load_path << "proto"
31
+ Protopuffs.load_message_classes
32
+
33
+ That makes the Bird message dynamically available in Ruby. Everything's
34
+ namespaced under <tt>Protopuffs::Message</tt>, which should help with your OCD.
35
+
36
+ bird = Protopuffs::Message::Bird.new
37
+ bird.name = "Sonny"
38
+ bird.species = "Cuculus canorus"
39
+
40
+ # encode this message to the super-efficient binary wire format
41
+ binary_bird = bird.to_wire_format
42
+
43
+ # or encode to the human-friendly text format, for debugging
44
+ puts bird.inspect
45
+
46
+ You can also decode incoming binary wire-format messages:
47
+
48
+ decoded_bird = Protopuffs::Message::Bird.new
49
+ decoded_bird.from_wire_format(binary_bird)
50
+ decoded_bird.name # => "Sonny"
51
+
52
+ === Mass-assignment
53
+
54
+ TODO: explain <tt>Message::Base.new</tt> with strings containing the wire format
55
+ or hashes, as well as <tt>#attributes=</tt>
56
+
57
+ == Missing functionality
58
+
59
+ Protopuffs currently only supports a base set of the <tt>.proto</tt> file
60
+ syntax. Here's what's missing:
61
+
62
+ * sfixed32 and sfixed64 types
63
+ * sint32 and sint64 types (due to lack of support for ZigZag encoding)
64
+ * packed repeated fields (the <tt>[packed=true]</tt> option)
65
+ * enumerations
66
+ * importing definitions
67
+ * nested message types
68
+ * extensions
69
+ * nested extensions
70
+ * packages
71
+ * services
72
+ * built-in options
73
+ * custom options
74
+ * groups (deprecated)
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 2
4
- :patch: 0
4
+ :patch: 1
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Protopuffs
2
4
  module Message
3
5
 
@@ -7,7 +9,7 @@ module Protopuffs
7
9
  attr_reader :fields
8
10
 
9
11
  def define_message_class(name, fields)
10
- name = name.capitalize
12
+ name = name.delete("_")
11
13
  self.check_fields_for_errors(name, fields)
12
14
  Message.send(:remove_const, name) if Message.const_defined?(name)
13
15
  klass = Message.const_set(name, Class.new(self))
@@ -36,27 +38,52 @@ module Protopuffs
36
38
 
37
39
  attr_reader :buffer
38
40
 
39
- def initialize
41
+ def initialize(field_values = nil)
40
42
  if self.class == Base
41
43
  raise "#{self.class} should not be instantiated directly. Use the factory #{self.class}.define_message_class instead."
42
44
  end
43
- @buffer = StringIO.new
45
+
46
+ if field_values.nil?
47
+ @buffer = StringIO.new
48
+ @buffer.set_encoding("BINARY") if @buffer.respond_to?(:set_encoding)
49
+ elsif field_values.respond_to?(:each_pair)
50
+ @buffer = StringIO.new
51
+ @buffer.set_encoding("BINARY") if @buffer.respond_to?(:set_encoding)
52
+ self.attributes = field_values
53
+ else
54
+ from_wire_format(field_values)
55
+ end
44
56
  end
45
57
 
46
58
  def to_wire_format
47
- self.class.fields.each do |field|
59
+ self.class.fields.sort_by { |f| f.tag }.each do |field|
48
60
  value = send(field.identifier)
49
61
  @buffer.write field.to_wire_format_with_value(value) unless value.nil?
50
62
  end
51
63
  @buffer.string
52
64
  end
53
65
 
66
+ # Returns the protocol buffer text format, which is useful for debugging
67
+ def inspect
68
+ type = self.class.name.split("::").last.downcase
69
+ field_strings = self.class.fields.map { |f| " #{f.identifier}: #{send(f.identifier).inspect}\n" }
70
+ "#{type} {\n#{field_strings.join}}"
71
+ end
72
+
54
73
  def from_wire_format(buffer)
55
- @buffer = buffer
74
+ if !buffer.respond_to?(:read)
75
+ buffer.force_encoding("BINARY") if buffer.respond_to?(:force_encoding)
76
+ @buffer = StringIO.new(buffer)
77
+ else
78
+ @buffer = buffer
79
+ end
80
+ @buffer.set_encoding("BINARY") if @buffer.respond_to?(:set_encoding)
81
+
56
82
  until @buffer.eof?
57
- tag, value_bytes = MessageField.shift_tag_and_value_bytes(@buffer)
83
+ tag = MessageField.shift_tag(@buffer)
58
84
  field = self.class.fields.find { |field| field.tag == tag }
59
85
  next if field.nil?
86
+ value_bytes = field.class.shift(@buffer)
60
87
 
61
88
  value = field.decode(value_bytes)
62
89
  if field.repeated? && send(field.identifier).nil?
@@ -68,6 +95,7 @@ module Protopuffs
68
95
  end
69
96
  end
70
97
  set_values_for_missing_optional_fields
98
+ self
71
99
  end
72
100
 
73
101
  def set_values_for_missing_optional_fields
@@ -76,6 +104,12 @@ module Protopuffs
76
104
  end
77
105
  end
78
106
 
107
+ def attributes=(attrs = {})
108
+ attrs.each_pair do |name, value|
109
+ self.send("#{name}=", value) if respond_to?("#{name}=")
110
+ end
111
+ end
112
+
79
113
  def ==(other)
80
114
  return false if self.class != other.class
81
115
  self.class.fields.each do |field|
@@ -1,40 +1,40 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Protopuffs
2
4
 
3
5
  class MessageField
4
- attr_reader :modifier, :type, :identifier, :tag, :default
6
+ attr_reader :identifier, :tag, :default
7
+
8
+ def self.factory(type, *args)
9
+ case type
10
+ when "int32" then Int32.new(*args)
11
+ when "int64" then Int64.new(*args)
12
+ when "uint32" then UInt32.new(*args)
13
+ when "uint64" then UInt64.new(*args)
14
+ when "bool" then Bool.new(*args)
15
+ when "double" then Double.new(*args)
16
+ when "fixed64" then Fixed64.new(*args)
17
+ when "string" then String.new(*args)
18
+ when "bytes" then Bytes.new(*args)
19
+ when "float" then Float.new(*args)
20
+ when "fixed32" then Fixed32.new(*args)
21
+ else Embedded.new(type, *args)
22
+ end
23
+ end
5
24
 
6
- def initialize(modifier, type, identifier, tag, default = nil)
25
+ def initialize(modifier, identifier, tag, default)
26
+ raise "MessageField is an abstract base class" if self.class == MessageField
27
+ raise ArgumentError.new("Invalid modifier '#{modifier}'") unless
28
+ ["optional", "required", "repeated"].include?(modifier)
7
29
  @modifier = modifier
8
- @type = type
9
30
  @identifier = identifier
10
31
  @tag = tag
11
32
  @default = default
12
-
13
- set_default_for_type if optional? && @default.nil?
14
- end
15
-
16
- def set_default_for_type
17
- if numeric?
18
- @default = 0
19
- elsif @type == "string"
20
- @default = ""
21
- elsif @type == "bool"
22
- @default = false
23
- end
24
- end
25
-
26
- def wire_type
27
- case @type
28
- when "int32", "int64", "uint32", "uint64", "bool" then WireType::VARINT
29
- when "double", "fixed64" then WireType::FIXED64
30
- when "string", "bytes" then WireType::LENGTH_DELIMITED
31
- when "float", "fixed32" then WireType::FIXED32
32
- else WireType::LENGTH_DELIMITED # embedded messages
33
- end
34
33
  end
34
+ private :initialize
35
35
 
36
36
  def key
37
- (@tag << 3) | wire_type
37
+ (@tag << 3) | self.class.wire_type
38
38
  end
39
39
 
40
40
  def repeated?
@@ -45,18 +45,8 @@ module Protopuffs
45
45
  @modifier == "optional"
46
46
  end
47
47
 
48
- def numeric?
49
- %w(double float int32 int64 uint32 unit64 sint32 sint64 fixed32 fixed64
50
- sfixed32 sfixed64).include?(@type)
51
- end
52
-
53
- def embedded_message?
54
- wire_type == WireType::LENGTH_DELIMITED && @type != "string" && @type != "bytes"
55
- end
56
-
57
48
  def to_wire_format_with_value(value)
58
- field_encoder = lambda { |val| self.class.varint_encode(key) + encode(val) }
59
-
49
+ field_encoder = lambda { |val| VarInt.encode(key) + self.class.encode(val) }
60
50
  if repeated?
61
51
  value.map(&field_encoder).join
62
52
  else
@@ -64,32 +54,60 @@ module Protopuffs
64
54
  end
65
55
  end
66
56
 
67
- def encode(value)
68
- case wire_type
69
- when WireType::VARINT
70
- value = (value ? 1 : 0) if @type == "bool"
71
- value_bytes = self.class.varint_encode(value)
72
- when WireType::LENGTH_DELIMITED
73
- if value.respond_to?(:to_wire_format)
74
- embedded_bytes = value.to_wire_format
75
- value_bytes = self.class.varint_encode(embedded_bytes.size) + embedded_bytes
76
- else
77
- value_bytes = self.class.varint_encode(value.size)
78
- value_bytes += self.class.string_encode(value) if @type == "string"
79
- value_bytes += value if @type == "bytes"
80
- end
81
- when WireType::FIXED32
82
- value_bytes = self.class.float_encode(value) if @type == "float"
83
- value_bytes = self.class.fixed32_encode(value) if @type == "fixed32"
84
- when WireType::FIXED64
85
- value_bytes = self.class.double_encode(value) if @type == "double"
86
- value_bytes = self.class.fixed64_encode(value) if @type == "fixed64"
57
+ def self.shift_tag(buffer)
58
+ bits = 0
59
+ bytes = VarInt.shift(buffer)
60
+ bytes.each_with_index do |byte, index|
61
+ byte &= 0b01111111
62
+ bits |= byte << (7 * index)
87
63
  end
88
- value_bytes
64
+ bits >> 3
89
65
  end
66
+ end
90
67
 
68
+ class Bool < MessageField
69
+ def initialize(modifier, identifier, tag, default = nil)
70
+ super(modifier, identifier, tag, default || false)
71
+ end
72
+ def self.wire_type; WireType::VARINT end
73
+ def self.shift(buffer); VarInt.shift(buffer) end
74
+ def decode(value_bytes)
75
+ value = VarInt.decode(value_bytes)
76
+ value = true if value == 1
77
+ value = false if value == 0
78
+ value
79
+ end
80
+ def self.encode(value); VarInt.encode(value ? 1 : 0) end
81
+ end
91
82
 
92
- def self.varint_encode(value)
83
+ class Numeric < MessageField
84
+ def initialize(modifier, identifier, tag, default = nil)
85
+ super(modifier, identifier, tag, default || 0)
86
+ end
87
+ end
88
+
89
+ class VarInt < Numeric
90
+ def self.wire_type; WireType::VARINT end
91
+ def self.shift(buffer)
92
+ bytes = []
93
+ begin
94
+ # Use #readbyte in Ruby 1.9, and #readchar in Ruby 1.8
95
+ byte = buffer.send(buffer.respond_to?(:readbyte) ? :readbyte : :readchar)
96
+ bytes << (byte & 0b01111111)
97
+ end while byte >> 7 == 1
98
+ bytes
99
+ end
100
+ def self.decode(bytes)
101
+ value = 0
102
+ bytes.each_with_index do |byte, index|
103
+ value |= byte << (7 * index)
104
+ end
105
+ value
106
+ end
107
+ def decode(bytes)
108
+ VarInt.decode(bytes)
109
+ end
110
+ def self.encode(value)
93
111
  return [0].pack('C') if value.zero?
94
112
  bytes = []
95
113
  until value.zero?
@@ -104,123 +122,96 @@ module Protopuffs
104
122
  bytes[-1] &= 0b01111111
105
123
  bytes.pack('C*')
106
124
  end
125
+ end
107
126
 
108
- def self.string_encode(value)
109
- value.unpack('U*').pack('C*')
110
- end
111
-
112
- def self.float_encode(value)
113
- [value].pack('e')
114
- end
127
+ class Int32 < VarInt; end
128
+ class Int64 < VarInt; end
129
+ class UInt32 < VarInt; end
130
+ class UInt64 < VarInt; end
115
131
 
116
- def self.double_encode(value)
117
- [value].pack('E')
118
- end
132
+ class Fixed32Base < Numeric
133
+ def self.wire_type; WireType::FIXED32 end
134
+ def self.shift(buffer); buffer.read(4) end
135
+ end
119
136
 
120
- def self.fixed64_encode(value)
121
- [value].pack('Q')
122
- end
137
+ class Fixed32 < Fixed32Base
138
+ def decode(bytes); bytes.unpack('V').first end
139
+ def self.encode(value); [value].pack('V') end
140
+ end
123
141
 
124
- def self.fixed32_encode(value)
125
- [value].pack('V')
126
- end
142
+ class Float < Fixed32Base
143
+ def decode(bytes); bytes.unpack('e').first end
144
+ def self.encode(value); [value].pack('e') end
145
+ end
127
146
 
128
- # note: returns two values
129
- def self.shift_tag_and_value_bytes(buffer)
130
- bits = 0
131
- bytes = shift_varint(buffer)
132
- bytes.each_with_index do |byte, index|
133
- byte &= 0b01111111
134
- bits |= byte << (7 * index)
135
- end
136
- wire_type = bits & 0b00000111
137
- tag = bits >> 3
138
-
139
- case wire_type
140
- when WireType::VARINT
141
- value_bytes = shift_varint(buffer)
142
- when WireType::LENGTH_DELIMITED
143
- value_bytes = shift_length_delimited(buffer)
144
- when WireType::FIXED32
145
- value_bytes = shift_fixed32(buffer)
146
- when WireType::FIXED64
147
- value_bytes = shift_fixed64(buffer)
148
- end
147
+ class Fixed64Base < Numeric
148
+ def self.wire_type; WireType::FIXED64 end
149
+ def self.shift(buffer); buffer.read(8) end
150
+ end
149
151
 
150
- [tag, value_bytes]
151
- end
152
+ class Fixed64 < Fixed64Base
153
+ def decode(bytes); bytes.unpack('Q').first end
154
+ def self.encode(value); [value].pack('Q') end
155
+ end
152
156
 
153
- def self.shift_varint(buffer)
154
- bytes = []
155
- begin
156
- byte = buffer.readchar
157
- bytes << (byte & 0b01111111)
158
- end while byte >> 7 == 1
159
- bytes
160
- end
157
+ class Double < Fixed64Base
158
+ def decode(bytes); bytes.unpack('E').first end
159
+ def self.encode(value); [value].pack('E') end
160
+ end
161
161
 
162
- def self.shift_length_delimited(buffer)
163
- bytes = shift_varint(buffer)
164
- value_length = self.varint_decode(bytes)
162
+ class LengthDelimited < MessageField
163
+ def self.wire_type; WireType::LENGTH_DELIMITED end
164
+ def self.shift(buffer)
165
+ bytes = VarInt.shift(buffer)
166
+ value_length = VarInt.decode(bytes)
165
167
  buffer.read(value_length)
166
168
  end
169
+ def decode(bytes); bytes end
170
+ end
167
171
 
168
- def self.shift_fixed32(buffer)
169
- buffer.read(4)
172
+ class String < LengthDelimited
173
+ def initialize(modifier, identifier, tag, default = nil)
174
+ super(modifier, identifier, tag, default || "")
170
175
  end
171
-
172
- def self.shift_fixed64(buffer)
173
- buffer.read(8)
176
+ def self.encode(value)
177
+ value = value.to_s
178
+ value.force_encoding("UTF-8") if value.respond_to?(:force_encoding)
179
+ # Use #bytesize in Ruby 1.9, and #size in Ruby 1.8
180
+ size = value.respond_to?(:bytesize) ? value.bytesize : value.size
181
+ VarInt.encode(size) + value
174
182
  end
175
-
176
- def decode(value_bytes)
177
- case wire_type
178
- when WireType::VARINT
179
- value = self.class.varint_decode(value_bytes)
180
- if @type == "bool"
181
- value = true if value == 1
182
- value = false if value == 0
183
- end
184
- when WireType::LENGTH_DELIMITED
185
- if embedded_message?
186
- value = Message.const_get(@type).new
187
- value.from_wire_format(StringIO.new(value_bytes))
188
- else
189
- value = value_bytes
190
- end
191
- when WireType::FIXED32
192
- value = self.class.float_decode(value_bytes) if @type == "float"
193
- value = self.class.fixed32_decode(value_bytes) if @type == "fixed32"
194
- when WireType::FIXED64
195
- value = self.class.double_decode(value_bytes) if @type == "double"
196
- value = self.class.fixed64_decode(value_bytes) if @type == "fixed64"
197
- end
198
- value
183
+ def decode(bytes)
184
+ bytes.respond_to?(:force_encoding) ? bytes.force_encoding("UTF-8") : bytes
199
185
  end
186
+ end
200
187
 
201
- def self.varint_decode(bytes)
202
- value = 0
203
- bytes.each_with_index do |byte, index|
204
- value |= byte << (7 * index)
205
- end
206
- value
188
+ class Bytes < LengthDelimited
189
+ def initialize(modifier, identifier, tag, default = nil)
190
+ super
207
191
  end
208
-
209
- def self.float_decode(bytes)
210
- bytes.unpack('e').first
192
+ def self.encode(value)
193
+ VarInt.encode(value.size) + value
211
194
  end
212
-
213
- def self.double_decode(bytes)
214
- bytes.unpack('E').first
195
+ def decode(bytes)
196
+ bytes.respond_to?(:force_encoding) ? bytes.force_encoding("BINARY") : bytes
215
197
  end
198
+ end
216
199
 
217
- def self.fixed64_decode(bytes)
218
- bytes.unpack('Q').first
200
+ class Embedded < LengthDelimited
201
+ def initialize(type, modifier, identifier, tag, default = nil)
202
+ @type = type
203
+ super(modifier, identifier, tag, default)
219
204
  end
220
-
221
- def self.fixed32_decode(bytes)
222
- bytes.unpack('V').first
205
+ def decode(bytes)
206
+ bytes.force_encoding("BINARY") if bytes.respond_to?(:force_encoding)
207
+ value = Message.const_get(@type.delete("_")).new
208
+ value.from_wire_format(StringIO.new(bytes))
209
+ end
210
+ def self.encode(value)
211
+ embedded_bytes = value.to_wire_format
212
+ VarInt.encode(embedded_bytes.size) + embedded_bytes
223
213
  end
224
214
  end
225
215
 
226
216
  end
217
+
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Protopuffs
2
4
  class WireType
3
5
  VARINT = 0
@@ -7,4 +9,4 @@ module Protopuffs
7
9
  END_GROUP = 4
8
10
  FIXED32 = 5
9
11
  end
10
- end
12
+ end
@@ -7,7 +7,7 @@ module Protopuffs
7
7
 
8
8
  class ProtocolBufferDescriptor
9
9
  def initialize
10
- Treetop.load "lib/protopuffs/parser/protocol_buffer"
10
+ Treetop.load File.join(File.dirname(__FILE__), "protocol_buffer")
11
11
  @parser = Protopuffs::ProtocolBufferParser.new
12
12
  end
13
13
 
@@ -21,4 +21,4 @@ module Protopuffs
21
21
  end
22
22
 
23
23
  end
24
- end
24
+ end
@@ -60,8 +60,8 @@ module Protopuffs
60
60
  end
61
61
 
62
62
  def build
63
- MessageField.new(modifier.text_value, type.text_value, identifier.text_value,
64
- integer.text_value.to_i, default)
63
+ MessageField.factory(type.text_value, modifier.text_value, identifier.text_value,
64
+ integer.text_value.to_i, default)
65
65
  end
66
66
  }
67
67
  end
data/lib/protopuffs.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ require "rubygems"
4
+ require "treetop"
1
5
  require "protopuffs/message/base"
2
6
  require "protopuffs/message/field"
3
7
  require "protopuffs/message/wire_type"
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require File.dirname(__FILE__) + '/test_helper'
2
4
 
3
5
  class AbstractSyntaxTreeTest < Test::Unit::TestCase
@@ -12,13 +14,13 @@ class AbstractSyntaxTreeTest < Test::Unit::TestCase
12
14
  @descriptor = "message Person {}"
13
15
  end
14
16
 
15
- should "create one MessageDescriptor" do
17
+ should "create one message class" do
16
18
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", [])
17
19
  @parser.parse(@descriptor)
18
20
  end
19
21
 
20
22
  should "not create any MessageFields" do
21
- Protopuffs::MessageField.expects(:new).never
23
+ Protopuffs::MessageField.expects(:factory).never
22
24
  @parser.parse(@descriptor)
23
25
  end
24
26
  end
@@ -32,14 +34,14 @@ class AbstractSyntaxTreeTest < Test::Unit::TestCase
32
34
  proto
33
35
  end
34
36
 
35
- should "create two MessageDescriptors" do
37
+ should "create two message classes" do
36
38
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Apple", []).in_sequence
37
39
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Orange", []).in_sequence
38
40
  @parser.parse(@descriptor)
39
41
  end
40
42
 
41
43
  should "not create any MessageFields" do
42
- Protopuffs::MessageField.expects(:new).never
44
+ Protopuffs::MessageField.expects(:factory).never
43
45
  @parser.parse(@descriptor)
44
46
  end
45
47
  end
@@ -54,13 +56,14 @@ class AbstractSyntaxTreeTest < Test::Unit::TestCase
54
56
  proto
55
57
  end
56
58
 
57
- should "create one 'Person' MessageDescriptor with one field" do
59
+ should "create one 'Person' message class with one field" do
58
60
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 1))
59
61
  @parser.parse(@descriptor)
60
62
  end
61
63
 
62
64
  should "create one MessageField with correct options" do
63
- Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).returns(stub(:tag => 1, :identifier => "name"))
65
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).
66
+ returns(stub(:tag => 1, :identifier => "name"))
64
67
  @parser.parse(@descriptor)
65
68
  end
66
69
  end
@@ -77,15 +80,18 @@ class AbstractSyntaxTreeTest < Test::Unit::TestCase
77
80
  proto
78
81
  end
79
82
 
80
- should "create one 'Person' MessageDescriptor with three fields" do
83
+ should "create one 'Person' message class with three fields" do
81
84
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
82
85
  @parser.parse(@descriptor)
83
86
  end
84
87
 
85
88
  should "create three MessageFields with correct options" do
86
- Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
87
- Protopuffs::MessageField.expects(:new).once.with("required", "int32", "id", 2, nil).in_sequence.returns(stub(:tag => 2, :identifier => "id"))
88
- Protopuffs::MessageField.expects(:new).once.with("optional", "string", "email", 3, nil).in_sequence.returns(stub(:tag => 3, :identifier => "email"))
89
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).in_sequence.
90
+ returns(stub(:tag => 1, :identifier => "name"))
91
+ Protopuffs::MessageField.expects(:factory).once.with("int32", "required", "id", 2, nil).in_sequence.
92
+ returns(stub(:tag => 2, :identifier => "id"))
93
+ Protopuffs::MessageField.expects(:factory).once.with("string", "optional", "email", 3, nil).in_sequence.
94
+ returns(stub(:tag => 3, :identifier => "email"))
89
95
  @parser.parse(@descriptor)
90
96
  end
91
97
  end
@@ -102,15 +108,18 @@ class AbstractSyntaxTreeTest < Test::Unit::TestCase
102
108
  proto
103
109
  end
104
110
 
105
- should "create one 'Person' MessageDescriptor with three fields" do
111
+ should "create one 'Person' message class with three fields" do
106
112
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
107
113
  @parser.parse(@descriptor)
108
114
  end
109
115
 
110
116
  should "create three MessageFields with correct options" do
111
- Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
112
- Protopuffs::MessageField.expects(:new).once.with("optional", "string", "language", 2, "en").in_sequence.returns(stub(:tag => 2, :identifier => "language"))
113
- Protopuffs::MessageField.expects(:new).once.with("optional", "int32", "account_code", 3, 0).in_sequence.returns(stub(:tag => 3, :identifier => "account_code"))
117
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).in_sequence.
118
+ returns(stub(:tag => 1, :identifier => "name"))
119
+ Protopuffs::MessageField.expects(:factory).once.with("string", "optional", "language", 2, "en").in_sequence.
120
+ returns(stub(:tag => 2, :identifier => "language"))
121
+ Protopuffs::MessageField.expects(:factory).once.with("int32", "optional", "account_code", 3, 0).in_sequence.
122
+ returns(stub(:tag => 3, :identifier => "account_code"))
114
123
  @parser.parse(@descriptor)
115
124
  end
116
125
  end
@@ -126,17 +135,19 @@ class AbstractSyntaxTreeTest < Test::Unit::TestCase
126
135
  proto
127
136
  end
128
137
 
129
- should "create one 'Person' MessageDescriptor with two fields" do
138
+ should "create one 'Person' message class with two fields" do
130
139
  Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 2))
131
140
  @parser.parse(@descriptor)
132
141
  end
133
142
 
134
143
  should "create two MessageFields with correct options" do
135
- Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
136
- Protopuffs::MessageField.expects(:new).once.with("repeated", "Address", "addresses", 2, nil).in_sequence.returns(stub(:tag => 2, :identifier => "addresses"))
144
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).in_sequence.
145
+ returns(stub(:tag => 1, :identifier => "name"))
146
+ Protopuffs::MessageField.expects(:factory).once.with("Address", "repeated", "addresses", 2, nil).in_sequence.
147
+ returns(stub(:tag => 2, :identifier => "addresses"))
137
148
  @parser.parse(@descriptor)
138
149
  end
139
150
  end
140
151
  end
141
152
 
142
- end
153
+ end
@@ -1,18 +1,20 @@
1
+ # encoding: UTF-8
2
+
1
3
  require File.dirname(__FILE__) + '/test_helper'
2
4
 
3
5
  class MessageBaseTest < Test::Unit::TestCase
4
6
 
5
7
  context ".define_message_class using 'Person' and two fields" do
6
8
  should "create a Message::Person class" do
7
- fields = [Protopuffs::MessageField.new("optional", "string", "name", 1),
8
- Protopuffs::MessageField.new("optional", "string", "address", 2)]
9
+ fields = [Protopuffs::String.new("optional", "name", 1),
10
+ Protopuffs::String.new("optional", "address", 2)]
9
11
  Protopuffs::Message::Base.define_message_class("Person", fields)
10
12
  Protopuffs::Message::Person
11
13
  end
12
14
 
13
15
  should "create a class with accessors for each field" do
14
- fields = [Protopuffs::MessageField.new("optional", "string", "name", 1),
15
- Protopuffs::MessageField.new("optional", "string", "address", 2)]
16
+ fields = [Protopuffs::String.new("optional", "name", 1),
17
+ Protopuffs::String.new("optional", "address", 2)]
16
18
  Protopuffs::Message::Base.define_message_class("Person", fields)
17
19
  person = Protopuffs::Message::Person.new
18
20
  person.name = "Chris"
@@ -25,8 +27,8 @@ class MessageBaseTest < Test::Unit::TestCase
25
27
 
26
28
  context ".define_message_class with fields that have duplicate tags" do
27
29
  should "raise a Protopuffs::ParseError" do
28
- fields = [Protopuffs::MessageField.new("optional", "int32", "name", 1),
29
- Protopuffs::MessageField.new("optional", "string", "address", 1)]
30
+ fields = [Protopuffs::Int32.new("optional", "name", 1),
31
+ Protopuffs::String.new("optional", "address", 1)]
30
32
  assert_raises Protopuffs::ParseError do
31
33
  Protopuffs::Message::Base.define_message_class("Person", fields)
32
34
  end
@@ -36,23 +38,23 @@ class MessageBaseTest < Test::Unit::TestCase
36
38
 
37
39
  context ".define_message_class with fields that have invalid tags" do
38
40
  should "raise a Protopuffs::ParseError when a tag is too large" do
39
- fields = [Protopuffs::MessageField.new("optional", "int32", "name", 1),
40
- Protopuffs::MessageField.new("optional", "string", "address", 536_870_912)]
41
+ fields = [Protopuffs::Int32.new("optional", "name", 1),
42
+ Protopuffs::String.new("optional", "address", 536_870_912)]
41
43
  assert_raises Protopuffs::ParseError do
42
44
  Protopuffs::Message::Base.define_message_class("Person", fields)
43
45
  end
44
46
  end
45
47
 
46
48
  should "raise a Protopuffs::ParseError when a tag is too small" do
47
- fields = [Protopuffs::MessageField.new("optional", "int32", "name", 0),
48
- Protopuffs::MessageField.new("optional", "string", "address", 1)]
49
+ fields = [Protopuffs::Int32.new("optional", "name", 0),
50
+ Protopuffs::String.new("optional", "address", 1)]
49
51
  assert_raises Protopuffs::ParseError do
50
52
  Protopuffs::Message::Base.define_message_class("Person", fields)
51
53
  end
52
54
  end
53
55
 
54
56
  should "raise a Protopuffs::ParseError when a tag is reserved" do
55
- fields = [Protopuffs::MessageField.new("optional", "string", "name", 19050)]
57
+ fields = [Protopuffs::String.new("optional", "name", 19050)]
56
58
  assert_raises Protopuffs::ParseError do
57
59
  Protopuffs::Message::Base.define_message_class("Person", fields)
58
60
  end
@@ -60,6 +62,30 @@ class MessageBaseTest < Test::Unit::TestCase
60
62
  end
61
63
 
62
64
 
65
+ context ".define_message_class with a message name that's Camel_Scored" do
66
+ # violates the Google style guide, but the other implementations handle this
67
+ should "strip the underscore when creating the class" do
68
+ Protopuffs::Message::Base.define_message_class("User_Image", [])
69
+ assert !Protopuffs::Message.const_defined?("User_Image")
70
+ assert Protopuffs::Message.const_defined?("UserImage")
71
+ end
72
+
73
+ context "and an embedded-message field that's Camel_Scored" do
74
+ should "strip the underscore when instantiating the class of the field" do
75
+ child_fields = [Protopuffs::String.new("optional", "street", 1)]
76
+ Protopuffs::Message::Base.define_message_class("User_Address", child_fields)
77
+ parent_fields = [Protopuffs::Embedded.new("User_Address", "required", "user_address", 1)]
78
+ Protopuffs::Message::Base.define_message_class("User_Info", parent_fields)
79
+
80
+ child = Protopuffs::Message::UserAddress.new(:street => "400 2nd Street")
81
+ parent = Protopuffs::Message::UserInfo.new(child.to_wire_format)
82
+
83
+ assert_equal Protopuffs::Message::UserAddress, parent.user_address.class
84
+ end
85
+ end
86
+ end
87
+
88
+
63
89
  should "not allow you to instantiate Message::Base directly" do
64
90
  assert_raises RuntimeError do
65
91
  Protopuffs::Message::Base.new
@@ -75,7 +101,7 @@ class MessageBaseTest < Test::Unit::TestCase
75
101
  end
76
102
 
77
103
  should "return false when the messages' fields have different values" do
78
- fields = [Protopuffs::MessageField.new("optional", "string", "name", 1)]
104
+ fields = [Protopuffs::String.new("optional", "name", 1)]
79
105
  Protopuffs::Message::Base.define_message_class("Person", fields)
80
106
  alice = Protopuffs::Message::Person.new
81
107
  alice.name = "Alice"
@@ -85,7 +111,7 @@ class MessageBaseTest < Test::Unit::TestCase
85
111
  end
86
112
 
87
113
  should "return true when messages of the same type have the same field values" do
88
- fields = [Protopuffs::MessageField.new("optional", "string", "name", 1)]
114
+ fields = [Protopuffs::String.new("optional", "name", 1)]
89
115
  Protopuffs::Message::Base.define_message_class("Sheep", fields)
90
116
  sheep = Protopuffs::Message::Sheep.new
91
117
  sheep.name = "Dolly"
@@ -95,4 +121,82 @@ class MessageBaseTest < Test::Unit::TestCase
95
121
  end
96
122
  end
97
123
 
98
- end
124
+
125
+ context "instantiating a message class" do
126
+ setup do
127
+ fields = [Protopuffs::String.new("required", "title", 2)]
128
+ Protopuffs::Message::Base.define_message_class("Book", fields)
129
+ end
130
+
131
+ should "optionally accept a wire-format encoded IO-style object and populate the fields" do
132
+ input = StringIO.new([0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67].pack("C*"))
133
+ message = Protopuffs::Message::Book.new(input)
134
+ assert_equal "testing", message.title
135
+ end
136
+
137
+ should "optionally accept a wire-format encoded string and populate the fields" do
138
+ input = [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67].pack("C*")
139
+ message = Protopuffs::Message::Book.new(input)
140
+ assert_equal "testing", message.title
141
+ end
142
+
143
+ should "not populate the fields if no argument is present" do
144
+ message = Protopuffs::Message::Book.new
145
+ assert_nil message.title
146
+ end
147
+
148
+ should "optionally accept a hash of field values and mass-assign them" do
149
+ message = Protopuffs::Message::Book.new(:title => "testing")
150
+ assert_equal "testing", message.title
151
+ end
152
+ end
153
+
154
+
155
+ context "mass-assignment of field values via #attributes=" do
156
+ setup do
157
+ fields = [Protopuffs::String.new("required", "title", 1),
158
+ Protopuffs::String.new("required", "author", 2),
159
+ Protopuffs::Int32.new("optional", "edition", 3)]
160
+ Protopuffs::Message::Base.define_message_class("Book", fields)
161
+ end
162
+
163
+ should "set each field value to the corresponding entry in the argument hash" do
164
+ message = Protopuffs::Message::Book.new
165
+ message.attributes = {:title => "You Shall Know Our Velocity",
166
+ :author => "Dave Eggers",
167
+ :edition => 2}
168
+ assert_equal "You Shall Know Our Velocity", message.title
169
+ assert_equal "Dave Eggers", message.author
170
+ assert_equal 2, message.edition
171
+ end
172
+
173
+ should "ignore unknown fields in the argument hash" do
174
+ message = Protopuffs::Message::Book.new
175
+ message.attributes = {:title => "You Shall Know Our Velocity",
176
+ :author => "Dave Eggers",
177
+ :edition => 2,
178
+ :isbn => "0970335555"}
179
+ assert_equal "You Shall Know Our Velocity", message.title
180
+ assert_equal "Dave Eggers", message.author
181
+ assert_equal 2, message.edition
182
+ end
183
+
184
+ should "only assign fields present in the argument hash" do
185
+ message = Protopuffs::Message::Book.new
186
+ message.title = "You Shall Know Our Velocity"
187
+ message.attributes = {:author => "Dave Eggers"}
188
+ assert_equal "Dave Eggers", message.author
189
+ assert_equal "You Shall Know Our Velocity", message.title
190
+ assert_nil message.edition
191
+ end
192
+
193
+ should "not assign defaults for optional fields missing from the argument hash" do
194
+ message = Protopuffs::Message::Book.new
195
+ message.attributes = {:author => "Dave Eggers"}
196
+ assert_equal "Dave Eggers", message.author
197
+ assert_nil message.title
198
+ assert_nil message.edition
199
+ end
200
+ end
201
+
202
+ end
@@ -1,34 +1,76 @@
1
+ # encoding: UTF-8
2
+
1
3
  require File.dirname(__FILE__) + '/test_helper'
2
4
 
3
5
  class MessageFieldTest < Test::Unit::TestCase
4
6
 
5
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
+ [ "embedded", Protopuffs::Embedded],
34
+ ].each do |type, klass|
35
+ assert_kind_of klass, Protopuffs::MessageField.factory(type, "optional", "a_string", 1, 2)
36
+ end
37
+ end
38
+
6
39
  should "set a string's default to '' when a default isn't specified" do
7
- field = Protopuffs::MessageField.new("optional", "string", "name", 1)
40
+ field = Protopuffs::String.new("optional", "name", 1)
41
+ assert_equal "", field.default
42
+ end
43
+
44
+ should "set a string's default to '' when a default is specified as nil" do
45
+ field = Protopuffs::String.new("optional", "name", 1, nil)
8
46
  assert_equal "", field.default
9
47
  end
10
48
 
11
- should "set a numeric's default to 0 when a default isn't specified" do
12
- numeric_types = %w(double float int32 int64 uint32 unit64 sint32 sint64
13
- fixed32 fixed64 sfixed32 sfixed64)
49
+ should "set a numeric's default to 0 when a default isn't specified or is specified as nil" do
50
+ numeric_types = [Protopuffs::Double, Protopuffs::Float,
51
+ Protopuffs::Int32, Protopuffs::Int64,
52
+ Protopuffs::UInt32, Protopuffs::UInt64,
53
+ Protopuffs::Fixed32, Protopuffs::Fixed64]
14
54
  numeric_types.each do |type|
15
- assert_equal 0, Protopuffs::MessageField.new("optional", type, "number", 1).default
55
+ assert_equal 0, type.new("optional", "number", 1).default
56
+ assert_equal 0, type.new("optional", "number", 1, nil).default
16
57
  end
17
58
  end
18
59
 
19
60
  should "set a bool's default to false when a default isn't specified" do
20
- field = Protopuffs::MessageField.new("optional", "bool", "opt_in", 1)
61
+ field = Protopuffs::Bool.new("optional", "opt_in", 1)
62
+ assert_same false, field.default
63
+ end
64
+
65
+ should "set a bool's default to false when a default is specified as nil" do
66
+ field = Protopuffs::Bool.new("optional", "opt_in", 1, nil)
21
67
  assert_same false, field.default
22
68
  end
23
69
 
24
70
  should "set the default to 'Matz' when that default is specified" do
25
- field = Protopuffs::MessageField.new("optional", "string", "name", 1, "Matz")
71
+ field = Protopuffs::String.new("optional", "name", 1, "Matz")
26
72
  assert_equal "Matz", field.default
27
73
  end
28
74
  end
29
75
 
30
- should_return_wire_type_for_fields_typed 0 => %w(int32 int64 uint32 uint64 bool),
31
- 1 => %w(double fixed64),
32
- 2 => %w(bytes string TestMessage),
33
- 5 => %w(float fixed32)
34
- end
76
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require File.dirname(__FILE__) + '/test_helper'
2
4
 
3
5
  class ParseTreeTest < Test::Unit::TestCase
@@ -203,4 +205,4 @@ class ParseTreeTest < Test::Unit::TestCase
203
205
 
204
206
  end
205
207
 
206
- end
208
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require File.dirname(__FILE__) + '/test_helper'
2
4
 
3
5
  class ProtopuffsTest < Test::Unit::TestCase
@@ -33,4 +35,4 @@ class ProtopuffsTest < Test::Unit::TestCase
33
35
  assert_equal ["Chris", 42, "chris@kampers.net"], [p.name, p.id, p.email]
34
36
  end
35
37
  end
36
- end
38
+ end
data/test/test_helper.rb CHANGED
@@ -1,8 +1,9 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'rubygems'
2
4
  require 'test/unit'
3
5
  require 'shoulda'
4
6
  require 'mocha'
5
- require 'treetop'
6
7
 
7
8
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
9
  require 'protopuffs'
@@ -37,6 +38,7 @@ class Test::Unit::TestCase
37
38
  def self.should_decode_wire_format_to_fields(actual_bytes, expected_fields)
38
39
  should "decode the byte string #{inspect_bytes(actual_bytes)} to the fields #{expected_fields.inspect}" do
39
40
  buffer = StringIO.new(actual_bytes.pack('C*'))
41
+ buffer.set_encoding("BINARY") if buffer.respond_to?(:set_encoding)
40
42
  @message.from_wire_format(buffer)
41
43
  actual_fields = @message.class.fields.inject({}) { |hash, field|
42
44
  hash[field.identifier.to_sym] = @message.send(field.identifier)
@@ -56,16 +58,5 @@ class Test::Unit::TestCase
56
58
  end
57
59
  end
58
60
 
59
-
60
- def self.should_return_wire_type_for_fields_typed(wire_types)
61
- wire_types.each_pair do |wire_type, names|
62
- names.each do |name|
63
- should "return wire type #{wire_type} for a field with type #{name}" do
64
- assert_equal wire_type, Protopuffs::MessageField.new("required", name, "a", 1).wire_type
65
- end
66
- end
67
- end
68
- end
69
-
70
61
  end
71
62
 
@@ -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
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require File.dirname(__FILE__) + '/test_helper'
2
4
 
3
5
  class WireFormatTest < Test::Unit::TestCase
@@ -5,7 +7,7 @@ class WireFormatTest < Test::Unit::TestCase
5
7
  context "a message with one int32 field tagged #1" do
6
8
  # from http://code.google.com/apis/protocolbuffers/docs/encoding.html#simple
7
9
  setup do
8
- fields = [Protopuffs::MessageField.new("required", "int32", "a", 1)]
10
+ fields = [Protopuffs::Int32.new("required", "a", 1)]
9
11
  Protopuffs::Message::Base.define_message_class("Test1", fields)
10
12
  @message = Protopuffs::Message::Test1.new
11
13
  end
@@ -15,13 +17,24 @@ class WireFormatTest < Test::Unit::TestCase
15
17
 
16
18
  # should ignore unknown fields: this message also has an int32 tagged #2 with value 157,372
17
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
18
31
  end
19
32
 
20
33
 
21
34
  context "a message with two int32 fields tagged #1 and #2" do
22
35
  setup do
23
- fields = [Protopuffs::MessageField.new("required", "int32", "a", 1),
24
- Protopuffs::MessageField.new("required", "int32", "b", 2)]
36
+ fields = [Protopuffs::Int32.new("required", "a", 1),
37
+ Protopuffs::Int32.new("required", "b", 2)]
25
38
  Protopuffs::Message::Base.define_message_class("Test1", fields)
26
39
  @message = Protopuffs::Message::Test1.new
27
40
  end
@@ -35,7 +48,7 @@ class WireFormatTest < Test::Unit::TestCase
35
48
 
36
49
  context "a message with one int64 field tagged #1" do
37
50
  setup do
38
- fields = [Protopuffs::MessageField.new("required", "int64", "a", 1)]
51
+ fields = [Protopuffs::Int64.new("required", "a", 1)]
39
52
  Protopuffs::Message::Base.define_message_class("Test1", fields)
40
53
  @message = Protopuffs::Message::Test1.new
41
54
  end
@@ -49,8 +62,8 @@ class WireFormatTest < Test::Unit::TestCase
49
62
 
50
63
  context "a message with one uint32 field tagged #1 and one uint64 field tagged #2" do
51
64
  setup do
52
- fields = [Protopuffs::MessageField.new("required", "uint32", "a", 1),
53
- Protopuffs::MessageField.new("required", "uint64", "b", 2)]
65
+ fields = [Protopuffs::UInt32.new("required", "a", 1),
66
+ Protopuffs::UInt64.new("required", "b", 2)]
54
67
  Protopuffs::Message::Base.define_message_class("Test1", fields)
55
68
  @message = Protopuffs::Message::Test1.new
56
69
  end
@@ -64,8 +77,8 @@ class WireFormatTest < Test::Unit::TestCase
64
77
 
65
78
  context "a message with two bool fields tagged #1 and #2" do
66
79
  setup do
67
- fields = [Protopuffs::MessageField.new("required", "bool", "a", 1),
68
- Protopuffs::MessageField.new("required", "bool", "b", 2)]
80
+ fields = [Protopuffs::Bool.new("required", "a", 1),
81
+ Protopuffs::Bool.new("required", "b", 2)]
69
82
  Protopuffs::Message::Base.define_message_class("Test1", fields)
70
83
  @message = Protopuffs::Message::Test1.new
71
84
  end
@@ -80,7 +93,7 @@ class WireFormatTest < Test::Unit::TestCase
80
93
  context "a message with one string field tagged #2" do
81
94
  setup do
82
95
  # from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
83
- fields = [Protopuffs::MessageField.new("required", "string", "b", 2)]
96
+ fields = [Protopuffs::String.new("required", "b", 2)]
84
97
  Protopuffs::Message::Base.define_message_class("Test2", fields)
85
98
  @message = Protopuffs::Message::Test2.new
86
99
  end
@@ -89,12 +102,25 @@ class WireFormatTest < Test::Unit::TestCase
89
102
  :b => "testing"
90
103
  should_decode_wire_format_to_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
91
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Ü"
92
118
  end
93
119
 
94
120
 
95
121
  context "a message with a bytes field tagged #1" do
96
122
  setup do
97
- fields = [Protopuffs::MessageField.new("required", "bytes", "a", 1)]
123
+ fields = [Protopuffs::Bytes.new("required", "a", 1)]
98
124
  Protopuffs::Message::Base.define_message_class("Test1", fields)
99
125
  @message = Protopuffs::Message::Test1.new
100
126
  end
@@ -108,7 +134,7 @@ class WireFormatTest < Test::Unit::TestCase
108
134
 
109
135
  context "a message with one float field tagged #1" do
110
136
  setup do
111
- fields = [Protopuffs::MessageField.new("required", "float", "a", 1)]
137
+ fields = [Protopuffs::Float.new("required", "a", 1)]
112
138
  Protopuffs::Message::Base.define_message_class("Test1", fields)
113
139
  @message = Protopuffs::Message::Test1.new
114
140
  end
@@ -124,7 +150,7 @@ class WireFormatTest < Test::Unit::TestCase
124
150
 
125
151
  context "a message with one double field tagged #1" do
126
152
  setup do
127
- fields = [Protopuffs::MessageField.new("required", "double", "a", 1)]
153
+ fields = [Protopuffs::Double.new("required", "a", 1)]
128
154
  Protopuffs::Message::Base.define_message_class("Test1", fields)
129
155
  @message = Protopuffs::Message::Test1.new
130
156
  end
@@ -140,7 +166,7 @@ class WireFormatTest < Test::Unit::TestCase
140
166
 
141
167
  context "a message with one fixed64 field tagged #1" do
142
168
  setup do
143
- fields = [Protopuffs::MessageField.new("required", "fixed64", "a", 1)]
169
+ fields = [Protopuffs::Fixed64.new("required", "a", 1)]
144
170
  Protopuffs::Message::Base.define_message_class("Test1", fields)
145
171
  @message = Protopuffs::Message::Test1.new
146
172
  end
@@ -155,7 +181,7 @@ class WireFormatTest < Test::Unit::TestCase
155
181
 
156
182
  context "a message with one fixed32 field tagged #1" do
157
183
  setup do
158
- fields = [Protopuffs::MessageField.new("required", "fixed32", "a", 1)]
184
+ fields = [Protopuffs::Fixed32.new("required", "a", 1)]
159
185
  Protopuffs::Message::Base.define_message_class("Test1", fields)
160
186
  @message = Protopuffs::Message::Test1.new
161
187
  end
@@ -169,7 +195,7 @@ class WireFormatTest < Test::Unit::TestCase
169
195
 
170
196
  context "a message with one repeating int32 field tagged #1" do
171
197
  setup do
172
- fields = [Protopuffs::MessageField.new("repeated", "int32", "a", 1)]
198
+ fields = [Protopuffs::Int32.new("repeated", "a", 1)]
173
199
  Protopuffs::Message::Base.define_message_class("Test1", fields)
174
200
  @message = Protopuffs::Message::Test1.new
175
201
  end
@@ -184,10 +210,10 @@ class WireFormatTest < Test::Unit::TestCase
184
210
  context "a message with one embedded-message field Test1 tagged #3 (where Test1 has an int32 field tagged #1)" do
185
211
  # from http://code.google.com/apis/protocolbuffers/docs/encoding.html#embedded
186
212
  setup do
187
- test1_fields = [Protopuffs::MessageField.new("required", "int32", "a", 1)]
213
+ test1_fields = [Protopuffs::Int32.new("required", "a", 1)]
188
214
  Protopuffs::Message::Base.define_message_class("Test1", test1_fields)
189
215
 
190
- test3_fields = [Protopuffs::MessageField.new("required", "Test1", "c", 3)]
216
+ test3_fields = [Protopuffs::Embedded.new("Test1", "required", "c", 3)]
191
217
  Protopuffs::Message::Base.define_message_class("Test3", test3_fields)
192
218
  @message = Protopuffs::Message::Test3.new
193
219
  end
@@ -201,8 +227,8 @@ class WireFormatTest < Test::Unit::TestCase
201
227
 
202
228
  context "a message with two int32 fields tagged #1 (optional, default=150) and #2 (required)" do
203
229
  setup do
204
- fields = [Protopuffs::MessageField.new("optional", "int32", "a", 1, 150),
205
- Protopuffs::MessageField.new("required", "int32", "b", 2)]
230
+ fields = [Protopuffs::Int32.new("optional", "a", 1, 150),
231
+ Protopuffs::Int32.new("required", "b", 2)]
206
232
  Protopuffs::Message::Base.define_message_class("Test1", fields)
207
233
  @message = Protopuffs::Message::Test1.new
208
234
  end
@@ -216,8 +242,8 @@ class WireFormatTest < Test::Unit::TestCase
216
242
 
217
243
  context "a message with two int32 fields tagged #1 (optional, no default) and #2 (required)" do
218
244
  setup do
219
- fields = [Protopuffs::MessageField.new("optional", "int32", "a", 1),
220
- Protopuffs::MessageField.new("required", "int32", "b", 2)]
245
+ fields = [Protopuffs::Int32.new("optional", "a", 1),
246
+ Protopuffs::Int32.new("required", "b", 2)]
221
247
  Protopuffs::Message::Base.define_message_class("Test1", fields)
222
248
  @message = Protopuffs::Message::Test1.new
223
249
  end
@@ -228,4 +254,25 @@ class WireFormatTest < Test::Unit::TestCase
228
254
  :a => 0, :b => 157_372
229
255
  end
230
256
 
231
- end
257
+
258
+ context "a message with two int32 fields tagged #1 and #2, defined with #2 first" do
259
+ setup do
260
+ fields = [Protopuffs::Int32.new("required", "b", 2),
261
+ Protopuffs::Int32.new("required", "a", 1)]
262
+ Protopuffs::Message::Base.define_message_class("Test1", fields)
263
+ @message = Protopuffs::Message::Test1.new
264
+ end
265
+
266
+ # should always encode with fields ordered by tag number, to take
267
+ # advantage of decoders that optimize for this case
268
+ should_encode_wire_format_from_fields [0x08, 0x14, 0x10, 0xBC, 0xCD, 0x09],
269
+ :a => 20, :b => 157_372
270
+
271
+ # the decoder should still support any field order, though
272
+ should_decode_wire_format_to_fields [0x08, 0x14, 0x10, 0xBC, 0xCD, 0x09],
273
+ :a => 20, :b => 157_372
274
+ should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09, 0x08, 0x14],
275
+ :a => 20, :b => 157_372
276
+ end
277
+
278
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chrisk-protopuffs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Kampmeier
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-09 00:00:00 -07:00
12
+ date: 2009-03-19 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -73,6 +73,7 @@ files:
73
73
  - test/parse_tree_test.rb
74
74
  - test/protopuffs_test.rb
75
75
  - test/test_helper.rb
76
+ - test/text_format_test.rb
76
77
  - test/wire_format_test.rb
77
78
  has_rdoc: true
78
79
  homepage: http://github.com/chrisk/protopuffs