duck_testing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.rspec +3 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +7 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +7 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +75 -0
- data/Rakefile +8 -0
- data/duck_testing.gemspec +28 -0
- data/lib/duck_testing.rb +19 -0
- data/lib/duck_testing/errors.rb +4 -0
- data/lib/duck_testing/method_call_data.rb +19 -0
- data/lib/duck_testing/reporter/base.rb +17 -0
- data/lib/duck_testing/reporter/raise_error.rb +30 -0
- data/lib/duck_testing/tester.rb +50 -0
- data/lib/duck_testing/type/base.rb +13 -0
- data/lib/duck_testing/type/class_instance.rb +24 -0
- data/lib/duck_testing/type/constant.rb +26 -0
- data/lib/duck_testing/type/duck_type.rb +24 -0
- data/lib/duck_testing/type/hash.rb +60 -0
- data/lib/duck_testing/type/order_dependent_array.rb +27 -0
- data/lib/duck_testing/type/order_independent_array.rb +27 -0
- data/lib/duck_testing/version.rb +3 -0
- data/lib/duck_testing/violation.rb +41 -0
- data/lib/duck_testing/yard.rb +51 -0
- data/lib/duck_testing/yard/builder.rb +70 -0
- data/lib/duck_testing/yard/class_object.rb +20 -0
- data/lib/duck_testing/yard/code_object.rb +22 -0
- data/lib/duck_testing/yard/method_object.rb +87 -0
- data/lib/duck_testing/yard/method_parameter.rb +49 -0
- data/lib/duck_testing/yard/parser.rb +30 -0
- data/sample/.gitignore +35 -0
- data/sample/.rspec +3 -0
- data/sample/Gemfile +5 -0
- data/sample/lib/concern.rb +9 -0
- data/sample/lib/sample.rb +14 -0
- data/sample/spec/sample_spec.rb +33 -0
- data/sample/spec/spec_helper.rb +4 -0
- data/spec/.rubocop.yml +8 -0
- data/spec/class_type_integration_spec.rb +98 -0
- data/spec/constant_type_integration_spec.rb +90 -0
- data/spec/duck_testing/method_call_data_spec.rb +24 -0
- data/spec/duck_testing/reporter/raise_error_spec.rb +35 -0
- data/spec/duck_testing/tester_spec.rb +73 -0
- data/spec/duck_testing/violation_spec.rb +58 -0
- data/spec/duck_testing/yard/builder_spec.rb +179 -0
- data/spec/duck_testing/yard/parser_spec.rb +38 -0
- data/spec/duck_type_integration_spec.rb +89 -0
- data/spec/hash_type_integration_spec.rb +112 -0
- data/spec/order_dependent_array_type_integration_spec.rb +121 -0
- data/spec/order_independent_array_type_integration_spec.rb +104 -0
- data/spec/spec_helper.rb +78 -0
- metadata +212 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module DuckTesting
|
2
|
+
module YARD
|
3
|
+
class MethodParameter
|
4
|
+
attr_reader :method_object, :name, :default, :parameter_tag
|
5
|
+
|
6
|
+
# @param method_object [DuckTesting::YARD::MethodObject]
|
7
|
+
# @param name [String]
|
8
|
+
# @param default [String]
|
9
|
+
# @param parameter_tag [YARD::Tags::Tag]
|
10
|
+
def initialize(method_object, name, default, parameter_tag)
|
11
|
+
@method_object = method_object
|
12
|
+
@name = name
|
13
|
+
@default = default
|
14
|
+
@parameter_tag = parameter_tag
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
def to_s
|
19
|
+
if default.nil?
|
20
|
+
name
|
21
|
+
elsif name.end_with?(":")
|
22
|
+
"#{name} #{default}"
|
23
|
+
else
|
24
|
+
"#{name} = #{default}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Boolean]
|
29
|
+
def documented?
|
30
|
+
!parameter_tag.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Boolean]
|
34
|
+
def keyword?
|
35
|
+
name.end_with?(":")
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Symbol, nil]
|
39
|
+
def key_name
|
40
|
+
keyword? ? name[0...-1].to_sym : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Array<DuckTesting::Type::Base>]
|
44
|
+
def expected_types
|
45
|
+
parameter_tag ? DuckTesting::YARD.expected_types(parameter_tag.types) : []
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "yard"
|
2
|
+
|
3
|
+
module DuckTesting
|
4
|
+
module YARD
|
5
|
+
class Parser
|
6
|
+
class << self
|
7
|
+
# Parses a path or set of paths.
|
8
|
+
#
|
9
|
+
# @param paths [String, Array<String>] a path, glob or list of paths to parse
|
10
|
+
# @param excluded [Array<String, Regexp>] a list of excluded path matchers
|
11
|
+
# @return [Array<DuckTesting::YARD::ClassObject>]
|
12
|
+
def parse(paths, excluded)
|
13
|
+
::YARD::Registry.clear
|
14
|
+
::YARD::Parser::SourceParser.parse(paths, excluded)
|
15
|
+
::YARD::Registry.all(:class).map { |class_object| ClassObject.new(class_object) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Parses a string `content`.
|
19
|
+
#
|
20
|
+
# @param content [String] the block of code to parse.
|
21
|
+
# @return [Array<DuckTesting::YARD::ClassObject>]
|
22
|
+
def parse_string(content)
|
23
|
+
::YARD::Registry.clear
|
24
|
+
::YARD::Parser::SourceParser.parse_string(content)
|
25
|
+
::YARD::Registry.all(:class).map { |class_object| ClassObject.new(class_object) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/sample/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
.ruby-version
|
31
|
+
.ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
35
|
+
spec/examples.txt
|
data/sample/.rspec
ADDED
data/sample/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "concern"
|
2
|
+
|
3
|
+
class Sample
|
4
|
+
include Concern
|
5
|
+
|
6
|
+
# Duplicate some text an arbitrary number of times.
|
7
|
+
#
|
8
|
+
# @param text [String] the string to be duplicated.
|
9
|
+
# @param count [Integer] the integer number of times to duplicate the `text`.
|
10
|
+
# @return [String] the duplicated string.
|
11
|
+
def multiplex(text, count)
|
12
|
+
text * count
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
describe Sample do
|
2
|
+
let(:sample) { described_class.new }
|
3
|
+
|
4
|
+
describe "#multiplex" do
|
5
|
+
subject { sample.multiplex(text, count) }
|
6
|
+
|
7
|
+
context "when valid parameters are given" do
|
8
|
+
let(:text) { "sample" }
|
9
|
+
let(:count) { 3 }
|
10
|
+
it { should eq "samplesamplesample" }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when invalid parameters are given" do
|
14
|
+
let(:text) { 3 }
|
15
|
+
let(:count) { 3 }
|
16
|
+
it { expect { subject }.to raise_error(DuckTesting::ContractViolationError) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#double" do
|
21
|
+
subject { sample.double(number) }
|
22
|
+
|
23
|
+
context "when valid parameters are given" do
|
24
|
+
let(:number) { 3 }
|
25
|
+
it { should eq 6 }
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when invalid parameters are given" do
|
29
|
+
let(:number) { "sample" }
|
30
|
+
it { expect { subject }.to raise_error(DuckTesting::ContractViolationError) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/.rubocop.yml
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
describe "Integration spec" do
|
2
|
+
subject do
|
3
|
+
instance.double(param)
|
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 number [Fixnum, Float]
|
15
|
+
# @return [Fixnum, Float]
|
16
|
+
def double(number)
|
17
|
+
number * 2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when expected parameter and return are given" do
|
23
|
+
let(:param) { 1 }
|
24
|
+
|
25
|
+
let(:duck_testing_module) do
|
26
|
+
Module.new do
|
27
|
+
def double(number)
|
28
|
+
tester = DuckTesting::Tester.new(self, "double")
|
29
|
+
tester.test_param(number, [
|
30
|
+
DuckTesting::Type::ClassInstance.new(Fixnum),
|
31
|
+
DuckTesting::Type::ClassInstance.new(Float)
|
32
|
+
])
|
33
|
+
tester.test_return(super, [
|
34
|
+
DuckTesting::Type::ClassInstance.new(Fixnum),
|
35
|
+
DuckTesting::Type::ClassInstance.new(Float)
|
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 2
|
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 double(number)
|
56
|
+
tester = DuckTesting::Tester.new(self, "double")
|
57
|
+
tester.test_param(number, [
|
58
|
+
DuckTesting::Type::ClassInstance.new(Fixnum),
|
59
|
+
DuckTesting::Type::ClassInstance.new(Float)
|
60
|
+
])
|
61
|
+
tester.test_return(super, [
|
62
|
+
DuckTesting::Type::ClassInstance.new(Fixnum),
|
63
|
+
DuckTesting::Type::ClassInstance.new(Float)
|
64
|
+
])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises ContractViolationError" do
|
70
|
+
expect { subject }.to raise_error(DuckTesting::ContractViolationError)
|
71
|
+
expect { subject }.to raise_error(/Contract violation for argument/)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when unexpected result is given" do
|
76
|
+
let(:param) { 1 }
|
77
|
+
|
78
|
+
let(:duck_testing_module) do
|
79
|
+
Module.new do
|
80
|
+
def double(number)
|
81
|
+
tester = DuckTesting::Tester.new(self, "double")
|
82
|
+
tester.test_param(number, [
|
83
|
+
DuckTesting::Type::ClassInstance.new(Fixnum),
|
84
|
+
DuckTesting::Type::ClassInstance.new(Float)
|
85
|
+
])
|
86
|
+
tester.test_return(super, [
|
87
|
+
DuckTesting::Type::ClassInstance.new(String)
|
88
|
+
])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "raises ContractViolationError" do
|
94
|
+
expect { subject }.to raise_error(DuckTesting::ContractViolationError)
|
95
|
+
expect { subject }.to raise_error(/Contract violation for return value/)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
describe "Constant type integration spec" do
|
2
|
+
subject do
|
3
|
+
instance.not(param)
|
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 a [Boolean]
|
15
|
+
# @return [Boolean]
|
16
|
+
def not(a)
|
17
|
+
!a
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when expected parameter and return are given" do
|
23
|
+
let(:param) { true }
|
24
|
+
|
25
|
+
let(:duck_testing_module) do
|
26
|
+
Module.new do
|
27
|
+
def not(a)
|
28
|
+
tester = DuckTesting::Tester.new(self, "not")
|
29
|
+
tester.test_param(a, [
|
30
|
+
DuckTesting::Type::Constant.new(true),
|
31
|
+
DuckTesting::Type::Constant.new(false)
|
32
|
+
])
|
33
|
+
tester.test_return(super, [
|
34
|
+
DuckTesting::Type::Constant.new(true),
|
35
|
+
DuckTesting::Type::Constant.new(false)
|
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 false
|
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 not(a)
|
56
|
+
tester = DuckTesting::Tester.new(self, "not")
|
57
|
+
tester.test_param(a, [
|
58
|
+
DuckTesting::Type::Constant.new(true),
|
59
|
+
DuckTesting::Type::Constant.new(false)
|
60
|
+
])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises ContractViolationError" do
|
66
|
+
expect { subject }.to raise_error(DuckTesting::ContractViolationError)
|
67
|
+
expect { subject }.to raise_error(/Contract violation for argument/)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when unexpected result is given" do
|
72
|
+
let(:param) { true }
|
73
|
+
|
74
|
+
let(:duck_testing_module) do
|
75
|
+
Module.new do
|
76
|
+
def not(a)
|
77
|
+
tester = DuckTesting::Tester.new(self, "not")
|
78
|
+
tester.test_return(super, [
|
79
|
+
DuckTesting::Type::Constant.new(true)
|
80
|
+
])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "raises ContractViolationError" do
|
86
|
+
expect { subject }.to raise_error(DuckTesting::ContractViolationError)
|
87
|
+
expect { subject }.to raise_error(/Contract violation for return value/)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
describe DuckTesting::MethodCallData do
|
2
|
+
let(:call_data) { described_class.new(*params) }
|
3
|
+
let(:params) { [receiver, method_name] }
|
4
|
+
let(:receiver) { nil }
|
5
|
+
let(:method_name) { nil }
|
6
|
+
|
7
|
+
describe "#method_expr" do
|
8
|
+
subject { call_data.method_expr }
|
9
|
+
|
10
|
+
let(:klass) do
|
11
|
+
Class.new do
|
12
|
+
def self.name
|
13
|
+
"Foo::Bar"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
let(:receiver) { klass.new }
|
18
|
+
let(:method_name) { "method_name" }
|
19
|
+
|
20
|
+
it 'returns "Class#method" style method name' do
|
21
|
+
should eq "#{klass.name}##{method_name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
describe DuckTesting::Reporter::RaiseError do
|
2
|
+
let(:reporter) { described_class.new(violation) }
|
3
|
+
let(:violation) { DuckTesting::Violation.new(violation_param) }
|
4
|
+
let(:violation_param) do
|
5
|
+
{ call_data: call_data,
|
6
|
+
expected_types: [] }
|
7
|
+
end
|
8
|
+
let(:call_data) { DuckTesting::MethodCallData.new("receiver", "mehtod_name") }
|
9
|
+
|
10
|
+
describe "#report" do
|
11
|
+
subject { reporter.report }
|
12
|
+
|
13
|
+
context "when violation#param? is true" do
|
14
|
+
before do
|
15
|
+
allow(violation).to receive(:param?).and_return(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raise ContractViolationError" do
|
19
|
+
expect { subject }.to raise_error(DuckTesting::ContractViolationError)
|
20
|
+
expect { subject }.to raise_error(/Contract violation for argument/)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when violation#return? is true" do
|
25
|
+
before do
|
26
|
+
allow(violation).to receive(:return?).and_return(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "raise ContractViolationError" do
|
30
|
+
expect { subject }.to raise_error(DuckTesting::ContractViolationError)
|
31
|
+
expect { subject }.to raise_error(/Contract violation for return value/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|