chrisk-protopuffs 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +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
|