duck_testing 0.0.1

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +15 -0
  5. data/.travis.yml +7 -0
  6. data/.yardopts +4 -0
  7. data/CHANGELOG.md +3 -0
  8. data/Gemfile +7 -0
  9. data/Guardfile +19 -0
  10. data/LICENSE +22 -0
  11. data/README.md +75 -0
  12. data/Rakefile +8 -0
  13. data/duck_testing.gemspec +28 -0
  14. data/lib/duck_testing.rb +19 -0
  15. data/lib/duck_testing/errors.rb +4 -0
  16. data/lib/duck_testing/method_call_data.rb +19 -0
  17. data/lib/duck_testing/reporter/base.rb +17 -0
  18. data/lib/duck_testing/reporter/raise_error.rb +30 -0
  19. data/lib/duck_testing/tester.rb +50 -0
  20. data/lib/duck_testing/type/base.rb +13 -0
  21. data/lib/duck_testing/type/class_instance.rb +24 -0
  22. data/lib/duck_testing/type/constant.rb +26 -0
  23. data/lib/duck_testing/type/duck_type.rb +24 -0
  24. data/lib/duck_testing/type/hash.rb +60 -0
  25. data/lib/duck_testing/type/order_dependent_array.rb +27 -0
  26. data/lib/duck_testing/type/order_independent_array.rb +27 -0
  27. data/lib/duck_testing/version.rb +3 -0
  28. data/lib/duck_testing/violation.rb +41 -0
  29. data/lib/duck_testing/yard.rb +51 -0
  30. data/lib/duck_testing/yard/builder.rb +70 -0
  31. data/lib/duck_testing/yard/class_object.rb +20 -0
  32. data/lib/duck_testing/yard/code_object.rb +22 -0
  33. data/lib/duck_testing/yard/method_object.rb +87 -0
  34. data/lib/duck_testing/yard/method_parameter.rb +49 -0
  35. data/lib/duck_testing/yard/parser.rb +30 -0
  36. data/sample/.gitignore +35 -0
  37. data/sample/.rspec +3 -0
  38. data/sample/Gemfile +5 -0
  39. data/sample/lib/concern.rb +9 -0
  40. data/sample/lib/sample.rb +14 -0
  41. data/sample/spec/sample_spec.rb +33 -0
  42. data/sample/spec/spec_helper.rb +4 -0
  43. data/spec/.rubocop.yml +8 -0
  44. data/spec/class_type_integration_spec.rb +98 -0
  45. data/spec/constant_type_integration_spec.rb +90 -0
  46. data/spec/duck_testing/method_call_data_spec.rb +24 -0
  47. data/spec/duck_testing/reporter/raise_error_spec.rb +35 -0
  48. data/spec/duck_testing/tester_spec.rb +73 -0
  49. data/spec/duck_testing/violation_spec.rb +58 -0
  50. data/spec/duck_testing/yard/builder_spec.rb +179 -0
  51. data/spec/duck_testing/yard/parser_spec.rb +38 -0
  52. data/spec/duck_type_integration_spec.rb +89 -0
  53. data/spec/hash_type_integration_spec.rb +112 -0
  54. data/spec/order_dependent_array_type_integration_spec.rb +121 -0
  55. data/spec/order_independent_array_type_integration_spec.rb +104 -0
  56. data/spec/spec_helper.rb +78 -0
  57. metadata +212 -0
@@ -0,0 +1,73 @@
1
+ describe DuckTesting::Tester do
2
+ let(:tester) { described_class.new(*constract_param) }
3
+ let(:constract_param) { [receiver, method_name] }
4
+ let(:receiver) { nil }
5
+ let(:method_name) { nil }
6
+
7
+ shared_context "param matches to expected_types", match: true do
8
+ let(:param) { 1 }
9
+ let(:expected_types) do
10
+ [
11
+ DuckTesting::Type::ClassInstance.new(Fixnum),
12
+ DuckTesting::Type::ClassInstance.new(Float)
13
+ ]
14
+ end
15
+ end
16
+
17
+ shared_context "param does not match to expected_types", match: false do
18
+ let(:param) { 1 }
19
+ let(:expected_types) do
20
+ [DuckTesting::Type::ClassInstance.new(Float)]
21
+ end
22
+ end
23
+
24
+ describe "#test_param" do
25
+ subject { tester.test_param(param, expected_types) }
26
+
27
+ context "when param matches to expected_types", match: true do
28
+ it "does not raise error" do
29
+ expect { subject }.not_to raise_error
30
+ end
31
+ end
32
+
33
+ context "when param does not match to expected_types", match: false do
34
+ it "raise ContractViolationError" do
35
+ expect { subject }.to raise_error(DuckTesting::ContractViolationError)
36
+ expect { subject }.to raise_error(/Contract violation for argument/)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#test_return" do
42
+ subject { tester.test_return(param, expected_types) }
43
+
44
+ context "when param matches to expected_types", match: true do
45
+ it "does not raise error" do
46
+ expect { subject }.not_to raise_error
47
+ end
48
+
49
+ it "returns the given param" do
50
+ should eq param
51
+ end
52
+ end
53
+
54
+ context "when param does not match to expected_types", match: false do
55
+ it "raise ContractViolationError" do
56
+ expect { subject }.to raise_error(DuckTesting::ContractViolationError)
57
+ expect { subject }.to raise_error(/Contract violation for return value/)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#match?" do
63
+ subject { tester.match?(param, expected_types) }
64
+
65
+ context "when param is a one of expected_types", match: true do
66
+ it { should be true }
67
+ end
68
+
69
+ context "when param is not a kind of expected_types", match: false do
70
+ it { should be false }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,58 @@
1
+ describe DuckTesting::Violation do
2
+ let(:violation) { described_class.new(params) }
3
+ let(:params) do
4
+ { call_data: call_data,
5
+ param: param,
6
+ expected_types: expected_types,
7
+ param_or_return: param_or_return }
8
+ end
9
+ let(:call_data) { nil }
10
+ let(:param) { nil }
11
+ let(:expected_types) { nil }
12
+ let(:param_or_return) { nil }
13
+
14
+ describe "#param?" do
15
+ subject { violation.param? }
16
+
17
+ context "when param_or_return is :param" do
18
+ let(:param_or_return) { :param }
19
+
20
+ it { should be true }
21
+ end
22
+
23
+ context "when param_or_return is :return" do
24
+ let(:param_or_return) { :return }
25
+
26
+ it { should be false }
27
+ end
28
+ end
29
+
30
+ describe "#return?" do
31
+ subject { violation.return? }
32
+
33
+ context "when param_or_return is :param" do
34
+ let(:param_or_return) { :param }
35
+
36
+ it { should be false }
37
+ end
38
+
39
+ context "when param_or_return is :return" do
40
+ let(:param_or_return) { :return }
41
+
42
+ it { should be true }
43
+ end
44
+ end
45
+
46
+ describe "#expected" do
47
+ subject { violation.expected }
48
+
49
+ let(:expected_types) do
50
+ [
51
+ DuckTesting::Type::ClassInstance.new(Fixnum),
52
+ DuckTesting::Type::ClassInstance.new(Float)
53
+ ]
54
+ end
55
+
56
+ it { should eq "Fixnum, Float" }
57
+ end
58
+ end
@@ -0,0 +1,179 @@
1
+ describe DuckTesting::YARD::Builder do
2
+ describe "#build" do
3
+ subject { builder.build(scope) }
4
+ let(:builder) { described_class.new(class_object) }
5
+ let(:class_object) { DuckTesting::YARD::Parser.parse_string(content).first }
6
+ let(:params) { [] }
7
+
8
+ context "when scope parameter is :instance" do
9
+ before { klass.send(:prepend, subject) }
10
+ let(:contents) { "Hello, World" }
11
+ let(:instance) { klass.new }
12
+ let(:run) { instance.bar(*params) }
13
+ let(:scope) { :instance }
14
+
15
+ context "and return tag comment is given" do
16
+ let(:content) do
17
+ <<-EOF
18
+ class Foo
19
+ # @!method bar
20
+ # @return [String]
21
+ end
22
+ EOF
23
+ end
24
+
25
+ context "and corresponding method returns valid type object" do
26
+ let(:klass) do
27
+ Class.new { def bar; "String" end }
28
+ end
29
+ it { expect { run }.not_to raise_error }
30
+ end
31
+
32
+ context "and corresponding method returns invalid type object" do
33
+ let(:klass) do
34
+ Class.new { def bar; :Symbol end }
35
+ end
36
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
37
+ end
38
+ end
39
+
40
+ context "and param tag comment is given" do
41
+ context "and there is a parameter without default value" do
42
+ let(:content) do
43
+ <<-EOF
44
+ class Foo
45
+ # @!method bar
46
+ # @param name [String]
47
+ end
48
+ EOF
49
+ end
50
+ let(:klass) do
51
+ Class.new { def bar(_name) end }
52
+ end
53
+ context "and valid type object is given as the parameter" do
54
+ let(:params) { ["String"] }
55
+ it { expect { run }.not_to raise_error }
56
+ end
57
+
58
+ context "and invalid type object is given as the parameter" do
59
+ let(:params) { [:String] }
60
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
61
+ end
62
+ end
63
+
64
+ context "and there is a parameter with default value" do
65
+ let(:content) do
66
+ <<-EOF
67
+ class Foo
68
+ # @!method bar
69
+ # @param name [String]
70
+ end
71
+ EOF
72
+ end
73
+ let(:klass) do
74
+ Class.new { def bar(_name = nil) end }
75
+ end
76
+
77
+ context "and the parameter is omitted" do
78
+ it { expect { run }.not_to raise_error }
79
+ end
80
+
81
+ context "and valid type object is given as the parameter" do
82
+ let(:params) { ["String"] }
83
+ it { expect { run }.not_to raise_error }
84
+ end
85
+
86
+ context "and invalid type object is given as the parameter" do
87
+ let(:params) { [:String] }
88
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
89
+ end
90
+ end
91
+
92
+ context "and there is a keyword parameter" do
93
+ let(:content) do
94
+ <<-EOF
95
+ class Foo
96
+ # @param key [String]
97
+ def bar(key: nil) end
98
+ end
99
+ EOF
100
+ end
101
+ let(:klass) do
102
+ Class.new { def bar(key: nil) end }
103
+ end
104
+
105
+ context "and valid type object is given as the parameter" do
106
+ let(:params) { [key: "String"] }
107
+ it { expect { run }.not_to raise_error }
108
+ end
109
+
110
+ context "and invalid type object is given as the parameter" do
111
+ let(:params) { [key: :Symbol] }
112
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
113
+ end
114
+ end
115
+
116
+ context "and there are both an normal paramter and a keyword parameter" do
117
+ let(:content) do
118
+ <<-EOF
119
+ class Foo
120
+ # @param name [String]
121
+ # @param key [String]
122
+ def bar(name, key: nil) end
123
+ end
124
+ EOF
125
+ end
126
+ let(:klass) do
127
+ Class.new { def bar(_name, key: nil) end }
128
+ end
129
+
130
+ context "and valid type objects are given as those parameters" do
131
+ let(:params) { ["String", key: "String"] }
132
+ it { expect { run }.not_to raise_error }
133
+ end
134
+
135
+ context "and invalid type object is given as the normal parameter" do
136
+ let(:params) { [:Symbol, key: "String"] }
137
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
138
+ end
139
+
140
+ context "and invalid type object is given as the keyword parameter" do
141
+ let(:params) { ["String", key: :Symbol] }
142
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ context "when scope parameter is :class" do
149
+ before { klass.singleton_class.send(:prepend, subject) }
150
+ let(:run) { klass.bar(*params) }
151
+ let(:scope) { :class }
152
+
153
+ context "and return tag comment is given" do
154
+ let(:content) do
155
+ <<-EOF
156
+ class Foo
157
+ # @!method self.bar
158
+ # @return [String]
159
+ end
160
+ EOF
161
+ end
162
+
163
+ context "and corresponding method returns valid type object" do
164
+ let(:klass) do
165
+ Class.new { def self.bar; "String" end }
166
+ end
167
+ it { expect { run }.not_to raise_error }
168
+ end
169
+
170
+ context "and corresponding method returns invalid type object" do
171
+ let(:klass) do
172
+ Class.new { def self.bar; :Symbol end }
173
+ end
174
+ it { expect { run }.to raise_error(DuckTesting::ContractViolationError) }
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,38 @@
1
+ describe DuckTesting::YARD::Parser do
2
+ describe ".parse_string" do
3
+ subject { described_class.parse_string(content) }
4
+ let(:content) do
5
+ <<-EOF
6
+ class Foo
7
+ # Reverses the contents of a String or IO object.
8
+ #
9
+ # @param [String, #read] contents the contents to reverse
10
+ # @return [String] the contents reversed lexically
11
+ def reverse(contents)
12
+ contents = contents.read if contents.respond_to? :read
13
+ contents.reverse
14
+ end
15
+ end
16
+ EOF
17
+ end
18
+
19
+ it "returns an array of DuckTesting::YARD::ClassObject" do
20
+ should all be_a DuckTesting::YARD::ClassObject
21
+
22
+ class_object = subject.first
23
+ expect(class_object.path).to eq "Foo"
24
+ expect(class_object.method_objects).to all be_a DuckTesting::YARD::MethodObject
25
+
26
+ method_object = class_object.method_objects.first
27
+ expect(method_object.path).to eq "Foo#reverse"
28
+ expect(method_object.method_parameters).to all be_a DuckTesting::YARD::MethodParameter
29
+
30
+ method_parameter = method_object.method_parameters.first
31
+ expect(method_parameter.name).to eq "contents"
32
+ expect(method_parameter.parameter_tag.types).to eq ["String", "#read"]
33
+
34
+ return_tag = method_object.return_tag
35
+ expect(return_tag.types).to eq ["String"]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,89 @@
1
+ require "stringio"
2
+
3
+ describe "Duck type integration spec" do
4
+ subject do
5
+ instance.read(param)
6
+ end
7
+
8
+ before do
9
+ klass.send(:prepend, duck_testing_module)
10
+ end
11
+
12
+ let(:instance) { klass.new }
13
+
14
+ let(:klass) do
15
+ Class.new do
16
+ # @param io [#read]
17
+ # @return [#read]
18
+ def read(io)
19
+ io
20
+ end
21
+ end
22
+ end
23
+
24
+ context "when expected parameter and return are given" do
25
+ let(:param) { StringIO.new }
26
+
27
+ let(:duck_testing_module) do
28
+ Module.new do
29
+ def read(io)
30
+ tester = DuckTesting::Tester.new(self, "read")
31
+ tester.test_param(io, [
32
+ DuckTesting::Type::DuckType.new("read")
33
+ ])
34
+ tester.test_return(super, [
35
+ DuckTesting::Type::DuckType.new("read")
36
+ ])
37
+ end
38
+ end
39
+ end
40
+
41
+ it "does not raise error" do
42
+ expect { subject }.not_to raise_error
43
+ end
44
+
45
+ it "returns original result" do
46
+ should eq param
47
+ end
48
+ end
49
+
50
+ context "when unexpected parameter is given" do
51
+ let(:param) { "string" }
52
+
53
+ let(:duck_testing_module) do
54
+ Module.new do
55
+ def read(io)
56
+ tester = DuckTesting::Tester.new(self, "read")
57
+ tester.test_param(io, [
58
+ DuckTesting::Type::DuckType.new("read")
59
+ ])
60
+ end
61
+ end
62
+ end
63
+
64
+ it "raises ContractViolationError" do
65
+ expect { subject }.to raise_error(DuckTesting::ContractViolationError)
66
+ expect { subject }.to raise_error(/Contract violation for argument/)
67
+ end
68
+ end
69
+
70
+ context "when unexpected result is given" do
71
+ let(:param) { "string" }
72
+
73
+ let(:duck_testing_module) do
74
+ Module.new do
75
+ def read(io)
76
+ tester = DuckTesting::Tester.new(self, "read")
77
+ tester.test_return(super, [
78
+ DuckTesting::Type::DuckType.new("read")
79
+ ])
80
+ end
81
+ end
82
+ end
83
+
84
+ it "raises ContractViolationError" do
85
+ expect { subject }.to raise_error(DuckTesting::ContractViolationError)
86
+ expect { subject }.to raise_error(/Contract violation for return value/)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,112 @@
1
+ describe "Hash type integration spec" do
2
+ subject do
3
+ instance.get(*params)
4
+ end
5
+
6
+ before do
7
+ klass.send(:prepend, duck_testing_module)
8
+ end
9
+
10
+ let(:instance) { klass.new }
11
+
12
+ let(:klass) do
13
+ Class.new do
14
+ # @param hash [Hash{Symbol => Object}]
15
+ # @param key [Symbol]
16
+ # @param default [Object]
17
+ # @return [Object]
18
+ def get(hash, key, default = nil)
19
+ hash.key?(key) ? hash[key] : default
20
+ end
21
+ end
22
+ end
23
+
24
+ context "when expected parameter and return are given" do
25
+ let(:params) { [{ a: 1, b: 2 }, :b, "hello"] }
26
+
27
+ let(:duck_testing_module) do
28
+ Module.new do
29
+ def get(hash, key, default = nil)
30
+ tester = DuckTesting::Tester.new(self, "get")
31
+ tester.test_param(hash, [
32
+ DuckTesting::Type::Hash.new(
33
+ [DuckTesting::Type::ClassInstance.new(Symbol)],
34
+ [DuckTesting::Type::ClassInstance.new(Object)]
35
+ )
36
+ ])
37
+ tester.test_param(key, [
38
+ DuckTesting::Type::ClassInstance.new(Symbol)
39
+ ])
40
+ tester.test_param(default, [
41
+ DuckTesting::Type::ClassInstance.new(Object)
42
+ ])
43
+ tester.test_return(super, [
44
+ DuckTesting::Type::ClassInstance.new(Object)
45
+ ])
46
+ end
47
+ end
48
+ end
49
+
50
+ it "does not raise error" do
51
+ expect { subject }.not_to raise_error
52
+ end
53
+
54
+ it "returns original result" do
55
+ should eq 2
56
+ end
57
+ end
58
+ context "when unexpected parameter is given" do
59
+ let(:params) { [{ "a" => 1 }, "a"] }
60
+
61
+ let(:duck_testing_module) do
62
+ Module.new do
63
+ def get(hash, _key, _default = nil)
64
+ tester = DuckTesting::Tester.new(self, "get")
65
+ tester.test_param(hash, [
66
+ DuckTesting::Type::Hash.new(
67
+ [DuckTesting::Type::ClassInstance.new(Symbol)],
68
+ [DuckTesting::Type::ClassInstance.new(Object)]
69
+ )
70
+ ])
71
+ end
72
+ end
73
+ end
74
+
75
+ it "raises ContractViolationError" do
76
+ expect { subject }.to raise_error(DuckTesting::ContractViolationError)
77
+ expect { subject }.to raise_error(/Contract violation for argument/)
78
+ end
79
+ end
80
+
81
+ context "when unexpected result is given" do
82
+ let(:params) { [{ a: 1, b: 2 }, :b, "hello"] }
83
+
84
+ let(:duck_testing_module) do
85
+ Module.new do
86
+ def get(hash, key, default = nil)
87
+ tester = DuckTesting::Tester.new(self, "get")
88
+ tester.test_param(hash, [
89
+ DuckTesting::Type::Hash.new(
90
+ [DuckTesting::Type::ClassInstance.new(Symbol)],
91
+ [DuckTesting::Type::ClassInstance.new(Object)]
92
+ )
93
+ ])
94
+ tester.test_param(key, [
95
+ DuckTesting::Type::ClassInstance.new(Symbol)
96
+ ])
97
+ tester.test_param(default, [
98
+ DuckTesting::Type::ClassInstance.new(Object)
99
+ ])
100
+ tester.test_return(super, [
101
+ DuckTesting::Type::ClassInstance.new(Symbol)
102
+ ])
103
+ end
104
+ end
105
+ end
106
+
107
+ it "raises ContractViolationError" do
108
+ expect { subject }.to raise_error(DuckTesting::ContractViolationError)
109
+ expect { subject }.to raise_error(/Contract violation for return value/)
110
+ end
111
+ end
112
+ end