transit-ruby 0.8.467

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ unless File.exist?('../transit-format/examples/0.8/simple')
16
+ puts <<-MSG
17
+ Before you can run the rspec examples, you need to install the
18
+ the https://github.com/cognitect/transit-format repo in a sibling
19
+ directory, e.g.
20
+
21
+ cd ..
22
+ git clone https://github.com/cognitect/transit-format
23
+
24
+ That repo contains exemplars used by transit-ruby's rspec examples
25
+ (in ../transit-format/examples/0.8/simple), so then you can:
26
+
27
+ cd transit-ruby
28
+ rspec
29
+
30
+ MSG
31
+ exit
32
+ end
33
+
34
+ require 'json'
35
+ require 'rspec'
36
+ require 'wrong/adapters/rspec'
37
+ require 'transit'
38
+ require 'spec_helper-local' if File.exist?(File.expand_path('../spec_helper-local.rb', __FILE__))
39
+
40
+ RSpec.configure do |c|
41
+ c.alias_example_to :fit, :focus => true
42
+ c.filter_run_including :focus => true, :focused => true
43
+ c.run_all_when_everything_filtered = true
44
+ c.mock_with :nothing
45
+ end
46
+
47
+ ALPHA_NUM = 'abcdefghijklmnopqrstuvwxyzABCDESFHIJKLMNOPQRSTUVWXYZ_0123456789'
48
+
49
+ def random_alphanum
50
+ ALPHA_NUM[rand(ALPHA_NUM.size)]
51
+ end
52
+
53
+ def random_string(max_length=10)
54
+ l = rand(max_length) + 1
55
+ (Array.new(l).map {|x| random_alphanum}).join
56
+ end
57
+
58
+ def random_strings(max_length=10, n=100)
59
+ Array.new(n).map {random_string(max_length)}
60
+ end
61
+
62
+ def random_symbol(max_length=10)
63
+ random_string(max_length).to_sym
64
+ end
65
+
66
+ def ints_centered_on(m, n=5)
67
+ ((m-n)..(m+n)).to_a
68
+ end
69
+
70
+ def array_of_symbols(m, n=m)
71
+ seeds = (0...m).map {|i| ("key%04d" % i).to_sym}
72
+ seeds.cycle.take(n)
73
+ end
74
+
75
+ def hash_of_size(n)
76
+ Hash[array_of_symbols(n).zip((0..n).to_a)]
77
+ end
78
+
79
+ Person = Struct.new("Person", :first_name, :last_name, :birthdate)
80
+
81
+ class PersonHandler
82
+ def tag(_) "person"; end
83
+ def rep(p) {:first_name => p.first_name, :last_name => p.last_name, :birthdate => p.birthdate} end
84
+ def string_rep(p) nil end
85
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe DateTimeUtil do
19
+ describe "[to|from]_millis" do
20
+ it "round trips properly" do
21
+ 100.times do
22
+ n = DateTime.now
23
+ a = Transit::DateTimeUtil.to_millis(n)
24
+ b = Transit::DateTimeUtil.from_millis(a)
25
+ c = Transit::DateTimeUtil.to_millis(b)
26
+ d = Transit::DateTimeUtil.from_millis(c)
27
+ assert { a == c }
28
+ assert { b == d }
29
+ sleep(0.0001)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "to_millis" do
35
+ let(:time) { Time.at(1388631845) + 0.678 }
36
+
37
+ it "supports DateTime" do
38
+ assert { Transit::DateTimeUtil.to_millis(time.to_datetime) == 1388631845678 }
39
+ end
40
+
41
+ it "supports Time" do
42
+ assert { Transit::DateTimeUtil.to_millis(time) == 1388631845678 }
43
+ end
44
+
45
+ it "supports Date" do
46
+ assert { Transit::DateTimeUtil.to_millis(Date.new(2014,1,2)) == 1388620800000 }
47
+ end
48
+ end
49
+
50
+ describe "from_millis" do
51
+ it "converts to utc" do
52
+ t = DateTime.now
53
+ m = Transit::DateTimeUtil.to_millis(t)
54
+ f = Transit::DateTimeUtil.from_millis(m)
55
+ assert { f.zone == '+00:00' }
56
+ end
57
+
58
+ it "handles millis properly" do
59
+ assert { Transit::DateTimeUtil.from_millis(1388631845674) == DateTime.new(2014,1,2,3,4,5.674).new_offset(0) }
60
+ assert { Transit::DateTimeUtil.from_millis(1388631845675) == DateTime.new(2014,1,2,3,4,5.675).new_offset(0) }
61
+ assert { Transit::DateTimeUtil.from_millis(1388631845676) == DateTime.new(2014,1,2,3,4,5.676).new_offset(0) }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe Decoder do
19
+ def decode(o)
20
+ Decoder.new.decode(o)
21
+ end
22
+
23
+ describe "caching" do
24
+ it "decodes cacheable map keys" do
25
+ assert { decode([{"this" => "a"},{"^0" => "b"}]) == [{"this" => "a"},{"this" => "b"}] }
26
+ end
27
+
28
+ it "does not cache non-map-keys" do
29
+ assert { decode([{"a" => "~^!"},{"b" => "~^?"}]) == [{"a" => "^!"},{"b" => "^?"}] }
30
+ end
31
+ end
32
+
33
+ describe "formats" do
34
+ describe "JSON_M" do
35
+ it "converts an array starting with '^ ' to a map" do
36
+ assert { decode(["^ ", :a, :b, :c, :d]) == {:a => :b, :c => :d} }
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "unrecognized input" do
42
+ it "decodes an unrecognized string to a TaggedValue" do
43
+ assert { decode("~Unrecognized") == TaggedValue.new("U", "nrecognized") }
44
+ end
45
+ end
46
+
47
+ describe "ints" do
48
+ it "decodes n as an Integer" do
49
+ 1.upto(64).each do |pow|
50
+ assert { decode("~n#{2**pow}").kind_of? Integer }
51
+ end
52
+ end
53
+ it "decodes i as an Integer" do
54
+ 1.upto(63).each do |pow|
55
+ assert { decode("~i#{2**pow - 1}").kind_of? Integer }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,149 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2014 Cognitect. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS-IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # -*- coding: utf-8 -*-
17
+ require 'spec_helper'
18
+
19
+ # Assumes that the examples are in the simple_examples dir at the top.
20
+
21
+ TOP_DIR=File.dirname(File.dirname(File.dirname(__FILE__)))
22
+
23
+ ARRAY_SIMPLE = [1, 2, 3]
24
+ ARRAY_MIXED = [0, 1, 2.0, true, false, 'five', :six, Transit::Symbol.new(:seven), '~eight', nil]
25
+ ARRAY_NESTED = [ARRAY_SIMPLE, ARRAY_MIXED]
26
+ SMALL_STRINGS = ["","a","ab","abc","abcd","abcde","abcdef"]
27
+ POWERS_OF_TWO = (0..65).map {|x| 2**x}
28
+ INTERESTING_INTS = (POWERS_OF_TWO.map {|x| ints_centered_on(x, 2)}).flatten
29
+
30
+ UUIDS = [
31
+ Transit::UUID.new('5a2cbea3-e8c6-428b-b525-21239370dd55'),
32
+ Transit::UUID.new('d1dc64fa-da79-444b-9fa4-d4412f427289'),
33
+ Transit::UUID.new('501a978e-3a3e-4060-b3be-1cf2bd4b1a38'),
34
+ Transit::UUID.new('b3ba141a-a776-48e4-9fae-a28ea8571f58')]
35
+
36
+ URIS = [
37
+ Addressable::URI.parse('http://example.com'),
38
+ Addressable::URI.parse('ftp://example.com'),
39
+ Addressable::URI.parse('file:///path/to/file.txt'),
40
+ Addressable::URI.parse('http://www.詹姆斯.com/')]
41
+
42
+ DATES = [-6106017600000, 0, 946728000000, 1396909037000].map {|x| Transit::DateTimeUtil.from_millis(x)}
43
+
44
+ SYMBOLS = [:a, :ab ,:abc ,:abcd, :abcde, :a1, :b2, :c3, :a_b]
45
+ TRANSIT_SYMBOLS = SYMBOLS.map {|x| Transit::Symbol.new(x)}
46
+
47
+ SET_SIMPLE = Set.new(ARRAY_SIMPLE)
48
+ SET_MIXED = Set.new(ARRAY_MIXED)
49
+ SET_NESTED= Set.new([SET_SIMPLE, SET_MIXED])
50
+
51
+ MAP_SIMPLE = {a: 1, b: 2, c: 3}
52
+ MAP_MIXED = {:a=>1, :b=>"a string", :c=>true}
53
+ MAP_NESTED = {simple: MAP_SIMPLE, mixed: MAP_MIXED}
54
+
55
+ Exemplar = Struct.new(:name, :expected_value)
56
+
57
+ EXEMPLARS = [
58
+ Exemplar.new('nil', nil),
59
+ Exemplar.new('true', true),
60
+ Exemplar.new('false', false),
61
+ Exemplar.new('zero', 0),
62
+ Exemplar.new('one', 1),
63
+ Exemplar.new('one_string', 'hello'),
64
+ Exemplar.new('one_keyword', :hello),
65
+ Exemplar.new('one_symbol', Transit::Symbol.new('hello')),
66
+ Exemplar.new('one_date', DateTime.new(2000,1,1,12)), # Transit::DateTimeUtil.from_millis(946728000000)),
67
+ Exemplar.new("vector_simple", ARRAY_SIMPLE),
68
+ Exemplar.new("vector_empty", []),
69
+ Exemplar.new("vector_mixed", ARRAY_MIXED),
70
+ Exemplar.new("vector_nested", ARRAY_NESTED),
71
+ Exemplar.new("small_strings", SMALL_STRINGS ),
72
+ Exemplar.new("strings_tilde", SMALL_STRINGS.map{|s| "~#{s}"}),
73
+ Exemplar.new("strings_hash", SMALL_STRINGS.map{|s| "##{s}"}),
74
+ Exemplar.new("strings_hat", SMALL_STRINGS.map{|s| "^#{s}"}),
75
+ Exemplar.new("small_ints", ints_centered_on(0)),
76
+ Exemplar.new("ints", (0...128).to_a),
77
+ Exemplar.new("ints_interesting", INTERESTING_INTS),
78
+ Exemplar.new("ints_interesting_neg", INTERESTING_INTS.map {|x| -1 * x}),
79
+ Exemplar.new("doubles_small", ints_centered_on(0).map {|x| Float(x)}),
80
+ Exemplar.new("doubles_interesting", [-3.14159, 3.14159, 4E11, 2.998E8, 6.626E-34]),
81
+ Exemplar.new('one_uuid', UUIDS.first),
82
+ Exemplar.new('uuids', UUIDS),
83
+ Exemplar.new('one_uri', URIS.first),
84
+ Exemplar.new('uris', URIS),
85
+ Exemplar.new('dates_interesting', DATES),
86
+ Exemplar.new('symbols', TRANSIT_SYMBOLS),
87
+ Exemplar.new('keywords', SYMBOLS),
88
+ Exemplar.new('list_simple', ARRAY_SIMPLE),
89
+ Exemplar.new('list_empty', []),
90
+ Exemplar.new('list_mixed', ARRAY_MIXED),
91
+ Exemplar.new('list_nested', [ARRAY_SIMPLE, ARRAY_MIXED]),
92
+ Exemplar.new('set_simple', SET_SIMPLE),
93
+ Exemplar.new("set_empty", Set.new),
94
+ Exemplar.new("set_mixed", SET_MIXED),
95
+ Exemplar.new("set_nested", SET_NESTED),
96
+ Exemplar.new('map_simple', MAP_SIMPLE),
97
+ Exemplar.new('map_mixed', MAP_MIXED),
98
+ Exemplar.new('map_nested', MAP_NESTED),
99
+ Exemplar.new('map_string_keys', {"first"=>1, "second"=>2, "third"=>3}),
100
+ Exemplar.new('map_numeric_keys', {1=>"one", 2=>"two"}),
101
+ Exemplar.new('map_vector_keys', {[1,1] => 'one', [2, 2] => 'two'}),
102
+ Exemplar.new('map_10_items', hash_of_size(10)),
103
+ Exemplar.new("maps_two_char_sym_keys", [{:aa=>1, :bb=>2}, {:aa=>3, :bb=>4}, {:aa=>5, :bb=>6}]),
104
+ Exemplar.new("maps_three_char_sym_keys", [{:aaa=>1, :bbb=>2}, {:aaa=>3, :bbb=>4}, {:aaa=>5, :bbb=>6}]),
105
+ Exemplar.new("maps_four_char_sym_keys", [{:aaaa=>1, :bbbb=>2}, {:aaaa=>3, :bbbb=>4}, {:aaaa=>5, :bbbb=>6}]),
106
+ Exemplar.new("maps_two_char_string_keys", [{'aa'=>1, 'bb'=>2}, {'aa'=>3, 'bb'=>4}, {'aa'=>5, 'bb'=>6}]),
107
+ Exemplar.new("maps_three_char_string_keys", [{'aaa'=>1, 'bbb'=>2}, {'aaa'=>3, 'bbb'=>4}, {'aaa'=>5, 'bbb'=>6}]),
108
+ Exemplar.new("maps_four_char_string_keys", [{'aaaa'=>1, 'bbbb'=>2}, {'aaaa'=>3, 'bbbb'=>4}, {'aaaa'=>5, 'bbbb'=>6}]),
109
+ Exemplar.new("maps_unrecognized_keys",
110
+ [Transit::TaggedValue.new("abcde", :anything), Transit::TaggedValue.new("fghij", :"anything-else")]),
111
+ Exemplar.new("map_unrecognized_vals", {key: "~Unrecognized"}),
112
+ Exemplar.new("vector_unrecognized_vals", ["~Unrecognized"]),
113
+ Exemplar.new("vector_1935_keywords_repeated_twice", array_of_symbols(1935, 3870)),
114
+ Exemplar.new("vector_1936_keywords_repeated_twice", array_of_symbols(1936, 3872)),
115
+ Exemplar.new("vector_1937_keywords_repeated_twice", array_of_symbols(1937, 3874))
116
+ ]
117
+
118
+ [10, 1935, 1936, 1937].each do |i|
119
+ EXEMPLARS << Exemplar.new( "map_#{i}_nested", {f: hash_of_size(i), s: hash_of_size(i)})
120
+ end
121
+
122
+ def verify_exemplar(exemplar, type, suffix)
123
+ path = "../transit-format/examples/0.8/simple/#{exemplar.name}#{suffix}"
124
+ it "reads what we expect from #{path}" do
125
+ raise "Can't open #{path}" unless File.exist?(path)
126
+ File.open(path) do |io|
127
+ actual_value = Transit::Reader.new(type, io).read
128
+ assert { exemplar.expected_value == actual_value }
129
+ end
130
+ end
131
+ end
132
+
133
+ module Transit
134
+ shared_examples "exemplars" do |type, suffix|
135
+ EXEMPLARS.each {|ex| verify_exemplar(ex, type, suffix)}
136
+ end
137
+
138
+ describe "JSON exemplars" do
139
+ include_examples "exemplars", :json, '.json'
140
+ end
141
+
142
+ describe "JSON-VERBOSE exemplars" do
143
+ include_examples "exemplars", :json_verbose, '.verbose.json'
144
+ end
145
+
146
+ describe "MessagePack exemplars" do
147
+ include_examples "exemplars", :msgpack, '.mp'
148
+ end
149
+ end
@@ -0,0 +1,129 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe Reader do
19
+ def read(value, &block)
20
+ reader = Reader.new(:json, StringIO.new(value.to_json, 'r+'))
21
+ reader.read &block
22
+ end
23
+
24
+ it 'reads without a block' do
25
+ assert { read([1,2,3]) == [1,2,3] }
26
+ end
27
+
28
+ it 'reads with a block' do
29
+ result = nil
30
+ read([1,2,3]) {|v| result = v}
31
+ assert { result == [1,2,3] }
32
+ end
33
+
34
+ describe 'handler registration' do
35
+ describe 'overrides' do
36
+ describe 'ground types' do
37
+ Decoder::GROUND_TAGS.each do |ground|
38
+ it "prevents override of #{ground} handler" do
39
+ assert {
40
+ rescuing {
41
+ Reader.new(:json, StringIO.new, :handlers => {ground => Object.new})
42
+ }.message =~ /ground types/ }
43
+ end
44
+ end
45
+ end
46
+
47
+ it 'supports override of default string handlers' do
48
+ io = StringIO.new("[\"~rhttp://foo.com\"]","r+")
49
+ reader = Reader.new(:json, io, :handlers => {"r" => Class.new { def from_rep(v) "DECODED: #{v}" end}.new})
50
+ assert { reader.read == ["DECODED: http://foo.com"] }
51
+ end
52
+
53
+ it 'supports override of default hash handlers' do
54
+ my_uuid_class = Class.new(String)
55
+ my_uuid = my_uuid_class.new(UUID.new.to_s)
56
+ io = StringIO.new({"~#u" => my_uuid.to_s}.to_json)
57
+ reader = Reader.new(:json, io, :handlers => {"u" => Class.new { define_method(:from_rep) {|v| my_uuid_class.new(v)}}.new})
58
+ assert { reader.read == my_uuid }
59
+ end
60
+
61
+ it 'supports override of the default handler' do
62
+ io = StringIO.new("~Xabc".to_json)
63
+ reader = Reader.new(:json, io, :default_handler => Class.new { def from_rep(tag,val) raise "Unacceptable: #{s}" end}.new)
64
+ assert { rescuing {reader.read }.message =~ /Unacceptable/ }
65
+ end
66
+ end
67
+
68
+ describe 'extensions' do
69
+ it 'supports string-based extensions' do
70
+ io = StringIO.new("~D2014-03-15".to_json)
71
+ reader = Reader.new(:json, io, :handlers => {"D" => Class.new { def from_rep(v) Date.parse(v) end}.new})
72
+ assert { reader.read == Date.new(2014,3,15) }
73
+ end
74
+
75
+ it 'supports hash based extensions' do
76
+ io = StringIO.new({"~#Times2" => 44}.to_json)
77
+ reader = Reader.new(:json, io, :handlers => {"Times2" => Class.new { def from_rep(v) v * 2 end}.new})
78
+ assert { reader.read == 88 }
79
+ end
80
+
81
+ it 'supports hash based extensions that return nil' do
82
+ io = StringIO.new({"~#Nil" => :anything}.to_json)
83
+ reader = Reader.new(:json, io, :handlers => {"Nil" => Class.new { def from_rep(_) nil end}.new})
84
+ assert { reader.read == nil }
85
+ end
86
+
87
+ it 'supports hash based extensions that return false' do
88
+ io = StringIO.new({"~#False" => :anything}.to_json)
89
+ reader = Reader.new(:json, io, :handlers => {"False" => Class.new { def from_rep(_) false end}.new})
90
+ assert { reader.read == false }
91
+ end
92
+
93
+ it 'supports complex hash values' do
94
+ io = StringIO.new([
95
+ {"~#person"=>
96
+ {"~:first_name" => "Transit","~:last_name" => "Ruby","~:birthdate" => "~D2014-01-02"}},
97
+ {"^0"=>
98
+ {"^1" => "Transit","^2" => "Ruby","^3" => "~D2014-01-03"}}].to_json)
99
+
100
+ person_handler = Class.new do
101
+ def from_rep(v)
102
+ Person.new(v[:first_name],v[:last_name],v[:birthdate])
103
+ end
104
+ end
105
+ date_handler = Class.new do
106
+ def from_rep(v) Date.parse(v) end
107
+ end
108
+ reader = Reader.new(:json, io,
109
+ :handlers => {
110
+ "person" => person_handler.new,
111
+ "D" => date_handler.new})
112
+ expected = [Person.new("Transit", "Ruby", Date.new(2014,1,2)),
113
+ Person.new("Transit", "Ruby", Date.new(2014,1,3))]
114
+ assert { reader.read == expected }
115
+ end
116
+ end
117
+
118
+ describe 'edge cases found in generative testing' do
119
+ it 'supports ...' do
120
+ io = StringIO.new(["~#cmap",[["~#ratio",["~n10","~n11"]],"~:foobar",["^1",["~n10","~n13"]],"^2"]].to_json)
121
+ reader = Reader.new(:json, io)
122
+ expected = {TaggedValue.new("ratio",[10,11]) => :foobar,
123
+ TaggedValue.new("ratio",[10,13]) => :foobar}
124
+ assert { reader.read == expected }
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end