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