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 +72 -1
- data/VERSION.yml +1 -1
- data/lib/protopuffs/message/base.rb +40 -6
- data/lib/protopuffs/message/field.rb +146 -155
- data/lib/protopuffs/message/wire_type.rb +3 -1
- data/lib/protopuffs/parser/parser.rb +2 -2
- data/lib/protopuffs/parser/protocol_buffer.treetop +2 -2
- data/lib/protopuffs.rb +4 -0
- data/test/abstract_syntax_tree_test.rb +29 -18
- data/test/message_base_test.rb +118 -14
- data/test/message_field_test.rb +54 -12
- data/test/parse_tree_test.rb +3 -1
- data/test/protopuffs_test.rb +3 -1
- data/test/test_helper.rb +3 -12
- data/test/text_format_test.rb +23 -0
- data/test/wire_format_test.rb +69 -22
- metadata +3 -2
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,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.
|
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
|
-
|
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
|
-
|
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
|
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 :
|
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,
|
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|
|
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
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
117
|
-
|
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
|
-
|
121
|
-
|
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
|
-
|
125
|
-
|
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
|
-
|
129
|
-
def self.
|
130
|
-
|
131
|
-
|
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
|
-
|
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
|
-
|
154
|
-
bytes
|
155
|
-
|
156
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
169
|
-
|
172
|
+
class String < LengthDelimited
|
173
|
+
def initialize(modifier, identifier, tag, default = nil)
|
174
|
+
super(modifier, identifier, tag, default || "")
|
170
175
|
end
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
210
|
-
bytes.unpack('e').first
|
192
|
+
def self.encode(value)
|
193
|
+
VarInt.encode(value.size) + value
|
211
194
|
end
|
212
|
-
|
213
|
-
|
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
|
-
|
218
|
-
|
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
|
-
|
222
|
-
|
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
|
+
|
@@ -7,7 +7,7 @@ module Protopuffs
|
|
7
7
|
|
8
8
|
class ProtocolBufferDescriptor
|
9
9
|
def initialize
|
10
|
-
Treetop.load "
|
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.
|
64
|
-
|
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,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
|
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(:
|
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
|
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(:
|
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'
|
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(:
|
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'
|
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(:
|
87
|
-
|
88
|
-
Protopuffs::MessageField.expects(:
|
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'
|
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(:
|
112
|
-
|
113
|
-
Protopuffs::MessageField.expects(:
|
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'
|
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(:
|
136
|
-
|
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
|
data/test/message_base_test.rb
CHANGED
@@ -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::
|
8
|
-
Protopuffs::
|
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::
|
15
|
-
Protopuffs::
|
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::
|
29
|
-
Protopuffs::
|
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::
|
40
|
-
Protopuffs::
|
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::
|
48
|
-
Protopuffs::
|
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::
|
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::
|
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::
|
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
|
-
|
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
|
data/test/message_field_test.rb
CHANGED
@@ -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::
|
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 =
|
13
|
-
|
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,
|
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::
|
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::
|
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
|
-
|
31
|
-
1 => %w(double fixed64),
|
32
|
-
2 => %w(bytes string TestMessage),
|
33
|
-
5 => %w(float fixed32)
|
34
|
-
end
|
76
|
+
end
|
data/test/parse_tree_test.rb
CHANGED
data/test/protopuffs_test.rb
CHANGED
@@ -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
|
data/test/wire_format_test.rb
CHANGED
@@ -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::
|
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::
|
24
|
-
Protopuffs::
|
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::
|
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::
|
53
|
-
Protopuffs::
|
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::
|
68
|
-
Protopuffs::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
205
|
-
Protopuffs::
|
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::
|
220
|
-
Protopuffs::
|
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
|
-
|
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.
|
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-
|
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
|