transit-ruby 0.8.467

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.
@@ -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