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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c61f7e63d611d677991d9cbcb05502a4bb8db7dd
4
+ data.tar.gz: 4f70acbf27363df964538e5bc42e3f72082f66ae
5
+ SHA512:
6
+ metadata.gz: 49fb0118fb936af49cf4e7b0c837c3f17d2b5ed06f87aaea02f9bc1857da41d5941224423f861c0511895f5abfae7cfc614fea45fd7fed6771bad839aaf263b9
7
+ data.tar.gz: ead4d4cc67a9117e93332c4a2b3ad573972dee042394c2c2f418806d074b1186c852da6e28511ff18cd8ed3ce88404cbdd778631edbf1c07d7b1aa3bf0d4ed73
@@ -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/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format doc
3
+ --require spec_helper
@@ -0,0 +1,15 @@
1
+ Style/StringLiterals:
2
+ EnforcedStyle: double_quotes
3
+
4
+ Metrics/LineLength:
5
+ Max: 100
6
+
7
+ Metrics/MethodLength:
8
+ Max: 25
9
+
10
+ # Document classes and non-namespace modules.
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ Style/TrailingComma:
15
+ Enabled: false
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0-p647
4
+ - 2.1.7
5
+ - 2.2.3
6
+ notifications:
7
+ email: false
@@ -0,0 +1,4 @@
1
+ --markup markdown
2
+ -
3
+ CHANGELOG.md
4
+ LICENSE
@@ -0,0 +1,3 @@
1
+ # 0.0.1
2
+
3
+ - Initial release.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "coveralls", require: false
7
+ end
@@ -0,0 +1,19 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ # Ruby files
12
+ ruby = dsl.ruby
13
+ dsl.watch_spec_files_for(ruby.lib_files)
14
+ end
15
+
16
+ guard :rubocop do
17
+ watch(/.+\.rb$/)
18
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
19
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Yuku TAKAHASHI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,75 @@
1
+ # DuckTesting
2
+
3
+ [![Build Status](https://travis-ci.org/yuku-t/duck_testing.svg?branch=master)](https://travis-ci.org/yuku-t/duck_testing) [![Code Climate](https://codeclimate.com/github/yuku-t/duck_testing/badges/gpa.svg)](https://codeclimate.com/github/yuku-t/duck_testing) [![Coverage Status](https://coveralls.io/repos/yuku-t/duck_testing/badge.svg)](https://coveralls.io/r/yuku-t/duck_testing)
4
+
5
+ Simple data type testing tool
6
+
7
+ ## Usage
8
+
9
+ ### YARD
10
+
11
+ ```rb
12
+ require "duck_testing"
13
+
14
+ DuckTesting::YARD.apply
15
+ ```
16
+
17
+ This code automatically generates duck_testing module for all classes in `{lib,app}/**/*.rb` and prepends them into corresponding classes. In most cases, you might put the code in `spec/spec_helper.rb` or `test/test_helper.rb`.
18
+
19
+ You can include and exclude custom paths by using `paths` and `excluded` arguments. For instance, excluding Rails' controllers and views is written as follows:
20
+
21
+ ```rb
22
+ DuckTesting::YARD.apply(excluded: [%r{^app/(controllers|views)}])
23
+ ```
24
+
25
+ ### Manually
26
+
27
+ Suppose there are a class and corresponding _duck_testing_ module:
28
+
29
+ ```rb
30
+ require "duck_testing"
31
+
32
+ class Foo
33
+ # @param [Fixnum, Float]
34
+ # @return [Fixnum, Float]
35
+ def double(x)
36
+ x * 2
37
+ end
38
+ end
39
+
40
+ module FooDuckTesting
41
+ def double(x)
42
+ tester = DuckTesting::Tester.new(self, "double")
43
+ tester.test_param(x, [
44
+ DuckTesting::Type::ClassInstance.new(Fixnum),
45
+ DuckTesting::Type::ClassInstance.new(Float)
46
+ ])
47
+ tester.test_return(super, [
48
+ DuckTesting::Type::ClassInstance.new(Fixnum),
49
+ DuckTesting::Type::ClassInstance.new(Float)
50
+ ])
51
+ end
52
+ end
53
+ ```
54
+
55
+ Now you can activate type testing by prepending _duck_testing_ module into the class:
56
+
57
+ ```rb
58
+ before = Foo.new
59
+
60
+ before.double("2")
61
+ # => "22"
62
+
63
+ Foo.send(:prepend, FooDuckTesting)
64
+
65
+ after = Foo.new
66
+
67
+ after.double(2)
68
+ # => 4
69
+
70
+ after.double(2.0)
71
+ # => 4.0
72
+
73
+ after.double("2")
74
+ # ContractViolationError: Contract violation for argument Foo#double
75
+ ```
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rubocop/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new("spec")
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: [:rubocop, :spec]
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require "duck_testing/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "duck_testing"
8
+ spec.version = DuckTesting::VERSION
9
+ spec.authors = ["Yuku Takahashi"]
10
+ spec.email = ["taka84u9@gmail.com"]
11
+ spec.summary = "Data type testing tool"
12
+ spec.description = "Data type testing tool"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.homepage = "https://github.com/yuku-t/duck_testing"
18
+
19
+ spec.add_dependency "yard", "~> 0.8.7"
20
+
21
+ spec.add_development_dependency "guard", "~> 2.12"
22
+ spec.add_development_dependency "guard-rspec", "~> 4.6"
23
+ spec.add_development_dependency "guard-rubocop", "~> 1.2"
24
+ spec.add_development_dependency "pry", "~> 0.10.3"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.3"
27
+ spec.add_development_dependency "rubocop", "~> 0.35.1"
28
+ end
@@ -0,0 +1,19 @@
1
+ require "duck_testing/errors"
2
+ require "duck_testing/method_call_data"
3
+ require "duck_testing/reporter/raise_error"
4
+ require "duck_testing/tester"
5
+ require "duck_testing/type/class_instance"
6
+ require "duck_testing/type/constant"
7
+ require "duck_testing/type/duck_type"
8
+ require "duck_testing/type/hash"
9
+ require "duck_testing/type/order_dependent_array"
10
+ require "duck_testing/type/order_independent_array"
11
+ require "duck_testing/version"
12
+ require "duck_testing/violation"
13
+ require "duck_testing/yard"
14
+ require "duck_testing/yard/builder"
15
+ require "duck_testing/yard/class_object"
16
+ require "duck_testing/yard/code_object"
17
+ require "duck_testing/yard/method_object"
18
+ require "duck_testing/yard/method_parameter"
19
+ require "duck_testing/yard/parser"
@@ -0,0 +1,4 @@
1
+ module DuckTesting
2
+ class ContractViolationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,19 @@
1
+ module DuckTesting
2
+ class MethodCallData
3
+ # @param receiver [Object] the receiver object.
4
+ # @param method_name [String] the invoked method's name.
5
+ def initialize(receiver, method_name)
6
+ @receiver = receiver
7
+ @method_name = method_name
8
+ end
9
+
10
+ # @return [String]
11
+ def method_expr
12
+ "#{receiver.class.name}##{method_name}"
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :receiver, :method_name
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module DuckTesting
2
+ module Reporter
3
+ class Base
4
+ attr_reader :violation
5
+
6
+ # @param violation [DuckTesting::Violation]
7
+ def initialize(violation)
8
+ @violation = violation
9
+ end
10
+
11
+ # Report contract violation.
12
+ def report
13
+ fail NotImplementedError
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ require "duck_testing/reporter/base"
2
+
3
+ module DuckTesting
4
+ module Reporter
5
+ class RaiseError < Base
6
+ # @note Override {DuckTesting::Reporter::Base#report}.
7
+ def report
8
+ fail ContractViolationError, failure_message
9
+ end
10
+
11
+ private
12
+
13
+ def failure_message
14
+ %(#{message_header}
15
+ Expected: #{violation.expected}
16
+ Actual: #{violation.param.inspect})
17
+ end
18
+
19
+ def message_header
20
+ if violation.param?
21
+ "Contract violation for argument of #{violation.method_expr}"
22
+ elsif violation.return?
23
+ "Contract violation for return value from #{violation.method_expr}"
24
+ else
25
+ fail NotImplementedError
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ module DuckTesting
2
+ class Tester
3
+ attr_reader :call_data
4
+
5
+ # @param receiver [Object] the receiver object.
6
+ # @param method_name [String] the invoked method's name.
7
+ def initialize(receiver, method_name)
8
+ @call_data = MethodCallData.new(receiver, method_name)
9
+ end
10
+
11
+ # @param param [Object]
12
+ # @param expected_types [Array<DuckTesting::Type::Base>]
13
+ def test_param(param, expected_types)
14
+ test(param, expected_types, :param)
15
+ end
16
+
17
+ # @param param [Object]
18
+ # @param expected_types [Array<DuckTesting::Type::Base>]
19
+ # @return [Object]
20
+ def test_return(param, expected_types)
21
+ test(param, expected_types, :return)
22
+ param
23
+ end
24
+
25
+ # @param param [Object]
26
+ # @param expected_types [Array<DuckTesting::Type::Base>]
27
+ # @return [Boolean]
28
+ def match?(param, expected_types)
29
+ expected_types.any? do |type|
30
+ type.match?(param)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # @param param [Object]
37
+ # @param expected_types [Array<DuckTesting::Type::Base>]
38
+ # @param type [Symbol] `:param` or `:return`
39
+ def test(param, expected_types, type)
40
+ return if match?(param, expected_types) || expected_types.empty?
41
+ violation = Violation.new(
42
+ call_data: call_data,
43
+ param: param,
44
+ expected_types: expected_types,
45
+ param_or_return: type
46
+ )
47
+ Reporter::RaiseError.new(violation).report
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ module DuckTesting
2
+ module Type
3
+ class Base
4
+ def match?(_object)
5
+ fail NotImplementedError
6
+ end
7
+
8
+ def to_s
9
+ fail NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require "duck_testing/type/base"
2
+
3
+ module DuckTesting
4
+ module Type
5
+ class ClassInstance < Base
6
+ attr_reader :klass
7
+
8
+ def initialize(klass)
9
+ @klass = klass
10
+ end
11
+
12
+ # @param object [Object]
13
+ # @return [Boolean]
14
+ def match?(object)
15
+ object.is_a?(klass)
16
+ end
17
+
18
+ # @return [String]
19
+ def to_s
20
+ klass.name
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ require "duck_testing/type/base"
2
+
3
+ module DuckTesting
4
+ module Type
5
+ class Constant < Base
6
+ CONSTANTS = %w(true false)
7
+
8
+ attr_reader :constant
9
+
10
+ def initialize(constant)
11
+ @constant = constant
12
+ end
13
+
14
+ # @param object [Object]
15
+ # @return [Boolean]
16
+ def match?(object)
17
+ object == constant
18
+ end
19
+
20
+ # @return [String]
21
+ def to_s
22
+ constant.to_s
23
+ end
24
+ end
25
+ end
26
+ end