inline_tests 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 57656c22e4b2934ea92a86653dceeea5eb02333bd4139aa0709a660be9537073
4
+ data.tar.gz: c6395566d89b424c025b70d268413d10055a547b97268332c1967d9e84433e9f
5
+ SHA512:
6
+ metadata.gz: ae97052ba781000565e759e560b1dcda519e3f09268487a5a7519398f95e902b47f60f57e5fe893d7400d63a7e1e2282ebf7878e82b818c17d2ee9ba3ece5e3b
7
+ data.tar.gz: 24ba65e9aa604b095c66452b6300520d3860b25560fcd554917f9bd43f1254ad8ff937fc408cc3af0ce5a04710dc5cccd66c6c00f7e5324d87f82746fefacfed
@@ -0,0 +1,16 @@
1
+ class InlineTestFailure < StandardError
2
+ attr_accessor :test_type, :lhs, :rhs, :description
3
+
4
+ def initialize(test_type=nil, lhs=nil, rhs=nil, description=nil)
5
+ super [
6
+ "#{description} FAILED:",
7
+ " test_type: #{test_type}",
8
+ " lhs: #{lhs}",
9
+ " rhs: #{rhs}"
10
+ ].join "\n"
11
+ self.test_type = test_type
12
+ self.lhs = lhs
13
+ self.rhs = rhs
14
+ self.description = description
15
+ end
16
+ end
@@ -0,0 +1,62 @@
1
+ require_relative './kernel_extensions'
2
+ require_relative './method_extensions'
3
+
4
+ require_relative './inline_test_failure'
5
+
6
+ class InlineTests
7
+ def self.run!
8
+ test_passes = 0
9
+ test_fails = 0
10
+
11
+ puts "Starting inline test suite:"
12
+ all_tests_start_time = Time.now
13
+ METHODS_WITH_INLINE_TESTS.each do |method|
14
+ # Kernel.class_variable_set(:@@method_being_tested, method)
15
+
16
+ method_signature = if method.receiver.class.name === Object.name
17
+ # If the receiver is an Object, it's probably #main, in which case we can just print it directly
18
+ "#{method.receiver}::#{method.name}"
19
+
20
+ elsif method.receiver.class.name == Class.name
21
+ # If the receiver is the base Class, then we're dealing with a class method so we have a class ref already
22
+ "#{method.receiver.name}::self.#{method.name}"
23
+
24
+ else
25
+ # If the receiver is some child class class, we want to use the class name in printable output
26
+ "#{method.receiver.class.name}::#{method.name}"
27
+ end
28
+
29
+ start_time = Time.now
30
+ begin
31
+ # TODO: it'd actually be great if we could bind #run_inline_tests to some context where all our
32
+ # asserts/testing stuff is defined so we don't have to clutter up the global Kernel namespace
33
+ method.run_inline_tests # Throws an exception on fail
34
+ test_time = Time.now - start_time
35
+ test_passes += 1
36
+
37
+ puts " #{method_signature} - PASSED (#{format_tiny_time test_time} seconds)"
38
+
39
+ rescue InlineTestFailure => failure_information
40
+ test_time = Time.now - start_time
41
+ errors = failure_information
42
+ test_fails += 1
43
+
44
+ puts " #{method_signature} - FAILED (#{format_tiny_time test_time} seconds)"
45
+ puts errors
46
+ end
47
+ end
48
+ all_tests_end_time = Time.now
49
+
50
+ # Print overall test results & stats
51
+ # print_results(method_passing, method_errors)
52
+ puts "#{METHODS_WITH_INLINE_TESTS.count} inline tests ran in #{format_tiny_time all_tests_end_time - all_tests_start_time} seconds."
53
+ puts " #{test_passes} PASSED"
54
+ puts " #{test_fails} FAILS"
55
+ end
56
+
57
+ private
58
+
59
+ def self.format_tiny_time(time)
60
+ sprintf('%.15f', time).sub(/0+$/, '').sub(/\.$/, '.0')
61
+ end
62
+ end
@@ -0,0 +1,117 @@
1
+ module Kernel
2
+ METHODS_WITH_INLINE_TESTS = []
3
+
4
+ RUN_TESTS_IN_THIS_ENVIRONMENT = true
5
+
6
+ def tested(method_name, _ignored, &inline_test_block)
7
+ return unless RUN_TESTS_IN_THIS_ENVIRONMENT
8
+
9
+ # Hoist the method variable outside of the block below so we can access it afterwards
10
+ method = nil
11
+
12
+ # The method definition exists in a different scope dependent on whether we're in a class or not.
13
+ if self.class.name === Object.name
14
+ # We're in `main` scope
15
+ method = method(method_name)
16
+
17
+ elsif self.class.name == Class.name
18
+ # We're in a class -- hunt for that method
19
+
20
+ if instance_methods.include?(method_name)
21
+ method = self.instance_method(method_name)
22
+
23
+ # Since the method is defined at script startup, it's an UnboundMethod until there's an instance to
24
+ # call it on. Obviously, need an instance to call it from during a test anyway, so we bind one here
25
+ # manually.
26
+ # TODO: this would be a great place to read defaults for some classes and apply attributes/defaults
27
+ # upon creation
28
+ instance = self.new
29
+ method = method.bind(instance)
30
+
31
+ elsif self.singleton_class.instance_methods.include?(method_name)
32
+ method = self.singleton_class.instance_method(method_name).bind(self)
33
+
34
+ end
35
+ end
36
+
37
+ # Register the method's tests to be run with InlineTests
38
+ method.inline_tests = inline_test_block
39
+ METHODS_WITH_INLINE_TESTS << method
40
+
41
+ method
42
+ end
43
+ def tests; end
44
+
45
+ # This is just syntax sugar for decorating methods that are untested / need tests
46
+ def untested(method_name); end
47
+
48
+ def assert(some_statement, description = '')
49
+ passed = !!some_statement
50
+ raise InlineTestFailure.new('assert', some_statement, nil, description) unless passed
51
+
52
+ passed
53
+ end
54
+
55
+ def assert_equal(lhs, rhs, description = '')
56
+ passed = !!flexible_assert(lhs, rhs, "[lhs] == [rhs]")
57
+ raise InlineTestFailure.new('assert_equal', lhs, rhs, description) unless passed
58
+
59
+ passed
60
+ end
61
+
62
+ def assert_not_equal(lhs, rhs, description = '')
63
+ passed = !!flexible_assert(lhs, rhs, "[lhs] != [rhs]")
64
+ raise InlineTestFailure.new('assert_not_equal', lhs, rhs, description) unless passed
65
+
66
+ passed
67
+ end
68
+
69
+ def assert_less_than(lhs, rhs, description = '')
70
+ passed = !!flexible_assert(lhs, rhs, "[lhs] < [rhs]")
71
+ raise InlineTestFailure.new('assert_less_than', lhs, rhs, description) unless passed
72
+
73
+ passed
74
+ end
75
+
76
+ def assert_greater_than(lhs, rhs, description = '')
77
+ passed = !!flexible_assert(lhs, rhs, "[lhs] > [rhs]")
78
+ raise InlineTestFailure.new('assert_greater_than', lhs, rhs, description) unless passed
79
+
80
+ passed
81
+ end
82
+
83
+ def assert_divisible_by(lhs, rhs, description = '')
84
+ passed = !!flexible_assert(lhs, rhs, "[lhs] % [rhs] == 0")
85
+ raise InlineTestFailure.new('assert_divisible_by', lhs, rhs, description) unless passed
86
+
87
+ passed
88
+ end
89
+
90
+ # TODO: assert_positive
91
+ # TODO: assert_negative
92
+
93
+ # dirty hacks for global constants :(
94
+ module Infinity; def to_s; Float::INFINITY; end; end
95
+ module NaN; def to_s; 0 * Float::INFINITY; end; end
96
+
97
+ private
98
+
99
+ def flexible_assert(lhs, rhs, assert_logic)
100
+ lhs_values = lhs
101
+ # todo: probably want a custom testresults class instead of hash
102
+ lhs_values = lhs.values if lhs.is_a? Hash
103
+ lhs_values = Array(lhs_values) unless lhs_values.is_a? Array
104
+
105
+ rhs_values = rhs
106
+ rhs_values = rhs.values if rhs.is_a? Hash
107
+ rhs_values = Array(rhs_values) unless rhs_values.is_a? Array
108
+
109
+ lhs_values.all? do |lhs|
110
+ rhs_values.all? do |rhs|
111
+ generated_source = assert_logic.gsub('[lhs]', lhs.to_s).gsub('[rhs]', rhs.to_s)
112
+ # puts "Debug: Evaluating #{generated_source}"
113
+ eval generated_source
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,37 @@
1
+ class Method
2
+ attr_accessor :inline_tests
3
+
4
+ # Expose a shorthand for .call
5
+ def [](*parameters)
6
+ homogenized_parameters = homogenized_list_of_arrays(parameters)
7
+
8
+ permutation_lookup = {}
9
+
10
+ all_range_permutations = permutations_of_list_of_ranges(homogenized_parameters)
11
+ all_range_permutations.each do |parameter_permutation|
12
+ permutation_lookup[parameter_permutation] = call(*parameter_permutation)
13
+ end
14
+
15
+ should_reduce_results = permutation_lookup.values.uniq.count == 1
16
+ if should_reduce_results
17
+ permutation_lookup.values.first
18
+ else
19
+ permutation_lookup
20
+ end
21
+ end
22
+
23
+ def run_inline_tests
24
+ inline_tests.call self if inline_tests && inline_tests.respond_to?(:call)
25
+ end
26
+
27
+ private
28
+
29
+ def homogenized_list_of_arrays(heterogenous_list)
30
+ heterogenous_list.map { |param| Array(param) }
31
+ end
32
+
33
+ def permutations_of_list_of_ranges(list_of_ranges)
34
+ receiver, *method_arguments = list_of_ranges
35
+ receiver.product(*method_arguments)
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inline_tests
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Brown
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-04-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A gem for including tests inline with the methods they test.
14
+ email: andrew@indentlabs.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/inline_test_failure.rb
20
+ - lib/inline_tests.rb
21
+ - lib/kernel_extensions.rb
22
+ - lib/method_extensions.rb
23
+ homepage: https://github.com/drusepth/inline-tests-gem
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.1.4
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: A gem for including tests inline with the methods they test.
46
+ test_files: []