protopuffs 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.specification +108 -0
- data/LICENSE.txt +18 -0
- data/README.rdoc +74 -0
- data/Rakefile +49 -0
- data/VERSION.yml +4 -0
- data/lib/protopuffs.rb +31 -0
- data/lib/protopuffs/message/base.rb +124 -0
- data/lib/protopuffs/message/field.rb +226 -0
- data/lib/protopuffs/message/wire_type.rb +12 -0
- data/lib/protopuffs/parser/parser.rb +24 -0
- data/lib/protopuffs/parser/protocol_buffer.treetop +124 -0
- data/protopuffs.gemspec +78 -0
- data/test/abstract_syntax_tree_test.rb +153 -0
- data/test/fixtures/proto/person.proto +5 -0
- data/test/message_base_test.rb +202 -0
- data/test/message_field_test.rb +78 -0
- data/test/parse_tree_test.rb +208 -0
- data/test/protopuffs_test.rb +38 -0
- data/test/test_helper.rb +90 -0
- data/test/text_format_test.rb +23 -0
- data/test/wire_format_test.rb +293 -0
- metadata +113 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Protopuffs
|
2
|
+
|
3
|
+
# The Protopuffs::Parser module holds wrapper classes that parse input
|
4
|
+
# strings using Treetop. They also set off the building of an abstract
|
5
|
+
# syntax tree from Treetop's parse tree.
|
6
|
+
module Parser
|
7
|
+
|
8
|
+
class ProtocolBufferDescriptor
|
9
|
+
def initialize
|
10
|
+
Treetop.load File.join(File.dirname(__FILE__), "protocol_buffer")
|
11
|
+
@parser = Protopuffs::ProtocolBufferParser.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(input)
|
15
|
+
parse_tree = @parser.parse(input)
|
16
|
+
parse_tree.build
|
17
|
+
parse_tree
|
18
|
+
rescue
|
19
|
+
raise ParseError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Protopuffs
|
2
|
+
grammar ProtocolBuffer
|
3
|
+
|
4
|
+
rule proto
|
5
|
+
s? proto_entries s? {
|
6
|
+
def messages
|
7
|
+
proto_entries.entries.select { |p| p.respond_to?(:message?) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def build
|
11
|
+
messages.each { |message| message.build }
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
rule proto_entries
|
17
|
+
proto_entry rest:(s? proto_entry)* {
|
18
|
+
def entries
|
19
|
+
rest.elements.inject([proto_entry]) { |a, s_and_pe| a << s_and_pe.proto_entry }
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
rule proto_entry
|
25
|
+
message {
|
26
|
+
def message?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
rule message
|
33
|
+
"message" s identifier s "{" s? body:message_body? s? "}" {
|
34
|
+
def name
|
35
|
+
identifier.text_value
|
36
|
+
end
|
37
|
+
|
38
|
+
def build
|
39
|
+
Message::Base.define_message_class(name, body.empty? ? [] : body.build)
|
40
|
+
end
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
rule message_body
|
45
|
+
field rest:(s? field)* {
|
46
|
+
def fields
|
47
|
+
rest.elements.inject([field]) { |a, s_and_field| a << s_and_field.field }
|
48
|
+
end
|
49
|
+
|
50
|
+
def build
|
51
|
+
fields.map { |field| field.build }
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
rule field
|
57
|
+
modifier s type s identifier s "=" s integer s? option:field_option? ";" {
|
58
|
+
def default
|
59
|
+
option.empty? ? nil : option.literal.obj
|
60
|
+
end
|
61
|
+
|
62
|
+
def build
|
63
|
+
MessageField.factory(type.text_value, modifier.text_value, identifier.text_value,
|
64
|
+
integer.text_value.to_i, default)
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
rule field_option
|
70
|
+
"[" s? "default" s? "=" s? literal s? "]"
|
71
|
+
end
|
72
|
+
|
73
|
+
rule modifier
|
74
|
+
"required" / "optional" / "repeated"
|
75
|
+
end
|
76
|
+
|
77
|
+
rule type
|
78
|
+
"double" / "float" / "int32" / "int64" / "uint32" / "uint64" /
|
79
|
+
"sint32" / "sint64" / "fixed32" / "fixed64" / "sfixed32" /
|
80
|
+
"sfixed64" / "bool" / "string" / "bytes" / identifier
|
81
|
+
end
|
82
|
+
|
83
|
+
rule literal
|
84
|
+
integer / string
|
85
|
+
end
|
86
|
+
|
87
|
+
rule integer
|
88
|
+
[\d]+ {
|
89
|
+
def obj
|
90
|
+
text_value.to_i
|
91
|
+
end
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
rule string
|
96
|
+
quote chars:[\w]* quote {
|
97
|
+
def obj
|
98
|
+
chars.text_value
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
rule quote
|
104
|
+
["']
|
105
|
+
end
|
106
|
+
|
107
|
+
rule identifier
|
108
|
+
[A-Za-z_] [\w_]*
|
109
|
+
end
|
110
|
+
|
111
|
+
rule s
|
112
|
+
(whitespace / comment_to_eol)+
|
113
|
+
end
|
114
|
+
|
115
|
+
rule comment_to_eol
|
116
|
+
'//' (!"\n" .)*
|
117
|
+
end
|
118
|
+
|
119
|
+
rule whitespace
|
120
|
+
[ \t\n\r]
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
data/protopuffs.gemspec
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{protopuffs}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Chris Kampmeier"]
|
12
|
+
s.date = %q{2009-09-21}
|
13
|
+
s.description = %q{A new implementation of Protocol Buffers in Ruby}
|
14
|
+
s.email = %q{chris@kampers.net}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
".specification",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION.yml",
|
26
|
+
"lib/protopuffs.rb",
|
27
|
+
"lib/protopuffs/message/base.rb",
|
28
|
+
"lib/protopuffs/message/field.rb",
|
29
|
+
"lib/protopuffs/message/wire_type.rb",
|
30
|
+
"lib/protopuffs/parser/parser.rb",
|
31
|
+
"lib/protopuffs/parser/protocol_buffer.treetop",
|
32
|
+
"protopuffs.gemspec",
|
33
|
+
"test/abstract_syntax_tree_test.rb",
|
34
|
+
"test/fixtures/proto/person.proto",
|
35
|
+
"test/message_base_test.rb",
|
36
|
+
"test/message_field_test.rb",
|
37
|
+
"test/parse_tree_test.rb",
|
38
|
+
"test/protopuffs_test.rb",
|
39
|
+
"test/test_helper.rb",
|
40
|
+
"test/text_format_test.rb",
|
41
|
+
"test/wire_format_test.rb"
|
42
|
+
]
|
43
|
+
s.homepage = %q{http://github.com/chrisk/protopuffs}
|
44
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
45
|
+
s.require_paths = ["lib"]
|
46
|
+
s.rubyforge_project = %q{protopuffs}
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
48
|
+
s.summary = %q{Sex, drugs, and protocol buffers}
|
49
|
+
s.test_files = [
|
50
|
+
"test/abstract_syntax_tree_test.rb",
|
51
|
+
"test/message_base_test.rb",
|
52
|
+
"test/message_field_test.rb",
|
53
|
+
"test/parse_tree_test.rb",
|
54
|
+
"test/protopuffs_test.rb",
|
55
|
+
"test/test_helper.rb",
|
56
|
+
"test/text_format_test.rb",
|
57
|
+
"test/wire_format_test.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_runtime_dependency(%q<treetop>, [">= 0"])
|
66
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<treetop>, [">= 0"])
|
70
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
71
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
72
|
+
end
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<treetop>, [">= 0"])
|
75
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
76
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class AbstractSyntaxTreeTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "a protocol buffer descriptor" do
|
8
|
+
setup do
|
9
|
+
@parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with an empty Person message" do
|
13
|
+
setup do
|
14
|
+
@descriptor = "message Person {}"
|
15
|
+
end
|
16
|
+
|
17
|
+
should "create one message class" do
|
18
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", [])
|
19
|
+
@parser.parse(@descriptor)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "not create any MessageFields" do
|
23
|
+
Protopuffs::MessageField.expects(:factory).never
|
24
|
+
@parser.parse(@descriptor)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
context "with two empty messages Apple and Orange" do
|
30
|
+
setup do
|
31
|
+
@descriptor = <<-proto
|
32
|
+
message Apple {}
|
33
|
+
message Orange {}
|
34
|
+
proto
|
35
|
+
end
|
36
|
+
|
37
|
+
should "create two message classes" do
|
38
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Apple", []).in_sequence
|
39
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Orange", []).in_sequence
|
40
|
+
@parser.parse(@descriptor)
|
41
|
+
end
|
42
|
+
|
43
|
+
should "not create any MessageFields" do
|
44
|
+
Protopuffs::MessageField.expects(:factory).never
|
45
|
+
@parser.parse(@descriptor)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
context "with a Person message including a name field" do
|
51
|
+
setup do
|
52
|
+
@descriptor = <<-proto
|
53
|
+
message Person {
|
54
|
+
required string name = 1;
|
55
|
+
}
|
56
|
+
proto
|
57
|
+
end
|
58
|
+
|
59
|
+
should "create one 'Person' message class with one field" do
|
60
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 1))
|
61
|
+
@parser.parse(@descriptor)
|
62
|
+
end
|
63
|
+
|
64
|
+
should "create one MessageField with correct options" do
|
65
|
+
Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).
|
66
|
+
returns(stub(:tag => 1, :identifier => "name"))
|
67
|
+
@parser.parse(@descriptor)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
context "with a Person message including three fields" do
|
73
|
+
setup do
|
74
|
+
@descriptor = <<-proto
|
75
|
+
message Person {
|
76
|
+
required string name = 1;
|
77
|
+
required int32 id = 2;
|
78
|
+
optional string email = 3;
|
79
|
+
}
|
80
|
+
proto
|
81
|
+
end
|
82
|
+
|
83
|
+
should "create one 'Person' message class with three fields" do
|
84
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
|
85
|
+
@parser.parse(@descriptor)
|
86
|
+
end
|
87
|
+
|
88
|
+
should "create three MessageFields with correct options" do
|
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"))
|
95
|
+
@parser.parse(@descriptor)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
context "with a message including two fields with defaults and one without" do
|
101
|
+
setup do
|
102
|
+
@descriptor = <<-proto
|
103
|
+
message Person {
|
104
|
+
required string name = 1;
|
105
|
+
optional string language = 2 [default = "en"];
|
106
|
+
optional int32 account_code = 3 [default = 0];
|
107
|
+
}
|
108
|
+
proto
|
109
|
+
end
|
110
|
+
|
111
|
+
should "create one 'Person' message class with three fields" do
|
112
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
|
113
|
+
@parser.parse(@descriptor)
|
114
|
+
end
|
115
|
+
|
116
|
+
should "create three MessageFields with correct options" do
|
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"))
|
123
|
+
@parser.parse(@descriptor)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
context "with a message including a user-typed field" do
|
129
|
+
setup do
|
130
|
+
@descriptor = <<-proto
|
131
|
+
message Person {
|
132
|
+
required string name = 1;
|
133
|
+
repeated Address addresses = 2;
|
134
|
+
}
|
135
|
+
proto
|
136
|
+
end
|
137
|
+
|
138
|
+
should "create one 'Person' message class with two fields" do
|
139
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 2))
|
140
|
+
@parser.parse(@descriptor)
|
141
|
+
end
|
142
|
+
|
143
|
+
should "create two MessageFields with correct options" do
|
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"))
|
148
|
+
@parser.parse(@descriptor)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class MessageBaseTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context ".define_message_class using 'Person' and two fields" do
|
8
|
+
should "create a Message::Person class" do
|
9
|
+
fields = [Protopuffs::String.new("optional", "name", 1),
|
10
|
+
Protopuffs::String.new("optional", "address", 2)]
|
11
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
12
|
+
Protopuffs::Message::Person
|
13
|
+
end
|
14
|
+
|
15
|
+
should "create a class with accessors for each field" do
|
16
|
+
fields = [Protopuffs::String.new("optional", "name", 1),
|
17
|
+
Protopuffs::String.new("optional", "address", 2)]
|
18
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
19
|
+
person = Protopuffs::Message::Person.new
|
20
|
+
person.name = "Chris"
|
21
|
+
person.address = "61 Carmelita St."
|
22
|
+
assert_equal person.name, "Chris"
|
23
|
+
assert_equal person.address, "61 Carmelita St."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
context ".define_message_class with fields that have duplicate tags" do
|
29
|
+
should "raise a Protopuffs::ParseError" do
|
30
|
+
fields = [Protopuffs::Int32.new("optional", "name", 1),
|
31
|
+
Protopuffs::String.new("optional", "address", 1)]
|
32
|
+
assert_raises Protopuffs::ParseError do
|
33
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
context ".define_message_class with fields that have invalid tags" do
|
40
|
+
should "raise a Protopuffs::ParseError when a tag is too large" do
|
41
|
+
fields = [Protopuffs::Int32.new("optional", "name", 1),
|
42
|
+
Protopuffs::String.new("optional", "address", 536_870_912)]
|
43
|
+
assert_raises Protopuffs::ParseError do
|
44
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
should "raise a Protopuffs::ParseError when a tag is too small" do
|
49
|
+
fields = [Protopuffs::Int32.new("optional", "name", 0),
|
50
|
+
Protopuffs::String.new("optional", "address", 1)]
|
51
|
+
assert_raises Protopuffs::ParseError do
|
52
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
should "raise a Protopuffs::ParseError when a tag is reserved" do
|
57
|
+
fields = [Protopuffs::String.new("optional", "name", 19050)]
|
58
|
+
assert_raises Protopuffs::ParseError do
|
59
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
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
|
+
|
89
|
+
should "not allow you to instantiate Message::Base directly" do
|
90
|
+
assert_raises RuntimeError do
|
91
|
+
Protopuffs::Message::Base.new
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
context "comparing messages with #==" do
|
97
|
+
should "return false when the messages have different types" do
|
98
|
+
Protopuffs::Message::Base.define_message_class("Dog", [])
|
99
|
+
Protopuffs::Message::Base.define_message_class("Cat", [])
|
100
|
+
assert_not_equal Protopuffs::Message::Dog.new, Protopuffs::Message::Cat.new
|
101
|
+
end
|
102
|
+
|
103
|
+
should "return false when the messages' fields have different values" do
|
104
|
+
fields = [Protopuffs::String.new("optional", "name", 1)]
|
105
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
106
|
+
alice = Protopuffs::Message::Person.new
|
107
|
+
alice.name = "Alice"
|
108
|
+
bob = Protopuffs::Message::Person.new
|
109
|
+
bob.name = "Bob"
|
110
|
+
assert_not_equal alice, bob
|
111
|
+
end
|
112
|
+
|
113
|
+
should "return true when messages of the same type have the same field values" do
|
114
|
+
fields = [Protopuffs::String.new("optional", "name", 1)]
|
115
|
+
Protopuffs::Message::Base.define_message_class("Sheep", fields)
|
116
|
+
sheep = Protopuffs::Message::Sheep.new
|
117
|
+
sheep.name = "Dolly"
|
118
|
+
sheep2 = Protopuffs::Message::Sheep.new
|
119
|
+
sheep2.name = "Dolly"
|
120
|
+
assert_equal sheep, sheep2
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
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
|