inline_tests 1.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 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: []