protopuffs 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|