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 +7 -0
- data/lib/inline_test_failure.rb +16 -0
- data/lib/inline_tests.rb +62 -0
- data/lib/kernel_extensions.rb +117 -0
- data/lib/method_extensions.rb +37 -0
- metadata +46 -0
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
|
data/lib/inline_tests.rb
ADDED
@@ -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: []
|