chrisk-protopuffs 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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