duck_testing 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,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
@@ -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
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format doc
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "duck_testing", path: "../"
4
+ gem "pry"
5
+ gem "rspec", "~> 3.3"
@@ -0,0 +1,9 @@
1
+ module Concern
2
+ # Calculate `number` multiplied by 2.
3
+ #
4
+ # @param number [Integer] the integer number to be doubled.
5
+ # @return [Integer] the doubled integer.
6
+ def double(number)
7
+ number * 2
8
+ end
9
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ require "sample"
2
+ require "duck_testing"
3
+
4
+ DuckTesting::YARD.apply
@@ -0,0 +1,8 @@
1
+ inherit_from:
2
+ - ../.rubocop.yml
3
+
4
+ Metrics/MethodLength:
5
+ Enabled: false
6
+
7
+ Style/SingleLineMethods:
8
+ Enabled: false
@@ -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