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