yard-bench 0.0.2
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/.document +11 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +35 -0
- data/config/cucumber.yml +6 -0
- data/examples/example_benchmark.rb +34 -0
- data/features/bm_dsl.feature +36 -0
- data/features/monkeypatches.feature +82 -0
- data/features/step_definitions/cukes_bm_dsl.rb +83 -0
- data/features/step_definitions/cukes_monkeypatches.rb +136 -0
- data/features/support/color-comment-formatter.rb +20 -0
- data/features/support/env.rb +5 -0
- data/lib/dsl/bm_dsl.rb +158 -0
- data/lib/dsl/monkeypatches.rb +334 -0
- data/lib/yard-bench/handler.rb +45 -0
- data/lib/yard-bench/version.rb +7 -0
- data/lib/yard-bench.rb +8 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/yard-bench_spec.rb +7 -0
- data/templates/default/method_details/html/benchmarks.erb +16 -0
- data/templates/default/method_details/setup.rb +6 -0
- data/templates/default/method_details/text/benchmarks.erb +15 -0
- data/yard-bench.gemspec +36 -0
- data/yard-bench.komodoproject +4 -0
- metadata +157 -0
data/lib/dsl/bm_dsl.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
require_relative 'monkeypatches'
|
5
|
+
|
6
|
+
# ⌚ :meth1, :meth2
|
7
|
+
# The information should be normalized and collected in a kind of knowledge base
|
8
|
+
module YARD
|
9
|
+
module Bench
|
10
|
+
# Class to be included for benchmarking DSL.
|
11
|
+
#
|
12
|
+
# It contains `Hash` of the following structure:
|
13
|
+
# —
|
14
|
+
class Marks
|
15
|
+
include YARD::MonkeyPatches
|
16
|
+
|
17
|
+
class Mark
|
18
|
+
attr_reader :times, :memory, :power, :deviation, :ok
|
19
|
+
def initialize(times, memory)
|
20
|
+
@times = times
|
21
|
+
@memory = memory
|
22
|
+
@ok = !@times.nil? && !@memory.nil?
|
23
|
+
normalized_times = @times.inject([]) { |agg, e| agg << (e.values[0] / (10**agg.size)) }
|
24
|
+
@deviation = normalized_times.standard_deviation * 100 / normalized_times.mean
|
25
|
+
@power = normalized_times.mean / @times[0].keys[0] * 1_000_000 if @ok && @deviation < 20
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
# Standard time for the current processor/ram to normalize benchmarks
|
31
|
+
STANDARD_TIME ||= Benchmark.measure { 1_000_000.times { "foo bar baz".capitalize }}.total
|
32
|
+
|
33
|
+
# Mark specified method of a class to be benchmarkable
|
34
|
+
#
|
35
|
+
# @param clazz [Class] the class method is defined on
|
36
|
+
# @param meth [Symbol] the method(s) to set as benchmarkable; may be a wildcard
|
37
|
+
# @return [Hash] a set of methods marked benchmarkable for the desired class
|
38
|
+
def self.∈ clazz, meth
|
39
|
+
get_methods(clazz, meth).map {|m| bm…(clazz)[:methods][m] ||= nil}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Mark specified method of a class to be benchmarkable and immediately benchmark
|
43
|
+
#
|
44
|
+
# @param clazz [Class] the class method is defined on
|
45
|
+
# @param meth [Symbol] the method(s) to set as benchmarkable; may be a wildcard
|
46
|
+
# @return [Hash] a set of methods marked benchmarkable for the desired class
|
47
|
+
def self.∈! clazz, meth
|
48
|
+
get_methods(clazz, meth).map { |m| bm…(clazz)[:methods][m] ||= self.mark(clazz, m) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Calculates benchmarks for all the marked methods
|
52
|
+
def self.⌛
|
53
|
+
bm… { |c, ms| # "String" => { :class => String, :methods => {…} }
|
54
|
+
ms[:methods].each { |m, marks| # { :capitalize => …, :split => nil }
|
55
|
+
ms[:methods][m] = self.mark(ms[:class], m) if marks.nil?
|
56
|
+
yield ms[:class], m, ms[:methods][m] if block_given?
|
57
|
+
}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def self.mark(clazz, m)
|
63
|
+
begin
|
64
|
+
(1..10).each { # Sometimes benchmark returns 0 for unknown reason. Ugly hack to mostly avoid.
|
65
|
+
mark = Mark.new(⌚(clazz, m), ☑(clazz, m))
|
66
|
+
break mark if mark.times[0].values[0] > 0
|
67
|
+
log.warn "Benchmarks returned zeroes: #{mark.times}, remeasuring…"
|
68
|
+
}
|
69
|
+
rescue
|
70
|
+
log.warn("Error calculating benchmarks: 〈#{$!}〉")
|
71
|
+
Mark.new(nil, nil)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# Get methods by their name with wildcards.
|
75
|
+
# @param clazz [Class] the class to retrieve methods for
|
76
|
+
# @param pattern [Symbol] the pattern to get method names for, either symbol,
|
77
|
+
# representing the method, or one of the wildcards “:×”, “:××”, “:⋅”, “:⋅⋅”
|
78
|
+
# @return [Array<Symbol>] an array of methods
|
79
|
+
def self.get_methods(clazz, pattern)
|
80
|
+
case pattern
|
81
|
+
# puts all the methods, defined in this class to benchmarks
|
82
|
+
when :⋅ then clazz.instance_methods(false)
|
83
|
+
# puts all the methods, defined in this class and superclasses to benchmarks
|
84
|
+
when :⋅⋅ then clazz.instance_methods(true)
|
85
|
+
# puts all the singleton methods, defined in this class to benchmarks
|
86
|
+
when :× then clazz.singleton_methods(false)
|
87
|
+
# puts all the singleton methods, defined in this class and superclasses to benchmarks
|
88
|
+
when :×× then clazz.singleton_methods(true)
|
89
|
+
else [pattern]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get all the benchmarks for the class. Lazy creates a `Set` to store
|
94
|
+
# benchmarks for future use if there is no benchmarks for the given class yet.
|
95
|
+
#
|
96
|
+
# @param clazz [Class] the class to return benchmarks for
|
97
|
+
# @return [Hash] all the benchmarks, collected from DSL as following
|
98
|
+
# benchmarks = {
|
99
|
+
# "String" => <Hash: {:class => String.class, :methods => {:capitalize => {benchmarks}, :split => nil}}>,
|
100
|
+
# "AClass" => <Hash: {:class => AClass.class, :methods => {:do_it => nil}}>
|
101
|
+
# }
|
102
|
+
def self.bm… clazz = nil
|
103
|
+
it = (@@benchmarks ||= {}) # Hash { String => { :capitalize => {…}, :split => nil }}
|
104
|
+
it = (it[clazz.to_s] ||= {:class => clazz, :methods => {}}) unless clazz.nil?
|
105
|
+
if block_given?
|
106
|
+
it.each(&Proc.new)
|
107
|
+
else
|
108
|
+
it
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
public
|
113
|
+
# Returns benchmarks for the method given by spec (or the whole collection if none specified)
|
114
|
+
def self.get file, namespace, m
|
115
|
+
load "#{file}"
|
116
|
+
self.∈! Object.const_get(namespace), m
|
117
|
+
end
|
118
|
+
|
119
|
+
# Measures the specified method of the class given
|
120
|
+
# @param clazz [Class] the class to measure method for
|
121
|
+
# @param m [Symbol] the method to measure
|
122
|
+
# @param iterations [Fixnum] an amount of iterations to do
|
123
|
+
# @return benchmarking total, normalized by STANDARD_TIME and 1_000_000 times
|
124
|
+
def self.⌚ clazz, m, iterations = 3
|
125
|
+
# Let’ calculate the applicable range
|
126
|
+
deg = (1..10).each { |v|
|
127
|
+
break v if Benchmark.measure { (10**v).times {clazz.☏ m} }.total > 0.01
|
128
|
+
}
|
129
|
+
(deg...deg+iterations).to_a.map { |d| 10**d }.map { |e|
|
130
|
+
{ e => Benchmark.measure { e.times {clazz.☏ m} }.total / STANDARD_TIME }
|
131
|
+
}
|
132
|
+
end
|
133
|
+
# Measures the memory required for a method in KBytes.
|
134
|
+
# FIXME This is VERY inaccurate and lame.
|
135
|
+
# @param clazz [Class] the class to measure method for
|
136
|
+
# @param m [Symbol] the method to measure
|
137
|
+
# @param iterations [Fixnum] an amount of iterations to do
|
138
|
+
# @return an approximate amount of kilobytes
|
139
|
+
def self.☑ clazz, m, iterations = 10
|
140
|
+
kb = `ps -o rss= -p #{$$}`.to_i
|
141
|
+
iterations.times.map {
|
142
|
+
clazz.☏ m
|
143
|
+
`ps -o rss= -p #{$$}`.to_i
|
144
|
+
}.reduce(:+) / iterations - kb
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module ::Kernel
|
149
|
+
# Mark the task for benchmarking
|
150
|
+
# @param attribs [[:rest]] the list of methods to benchmark
|
151
|
+
def ⌚ *attribs
|
152
|
+
attribs.each { |a| YARD::Bench::Marks.∈ self, a.to_sym }
|
153
|
+
end
|
154
|
+
alias benchmark ⌚
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module YARD
|
4
|
+
# Monkey patches (🐵) for shorthands.
|
5
|
+
#
|
6
|
+
# This module introduces new functionality
|
7
|
+
# for creation of some standard classes “samples.” It is used to emulate
|
8
|
+
# real data to be passed to automatic benchmarks in cases, when the methods
|
9
|
+
# have parameters required.
|
10
|
+
#
|
11
|
+
# @example To produce new random +String+, +Hash+, +Array+, +Fixnum+, one simply calls:
|
12
|
+
# % pry
|
13
|
+
# > require './lib/dsl/monkeypatches'
|
14
|
+
# # ⇒ true
|
15
|
+
# > String.∀ size: 30
|
16
|
+
# # ⇒ "3XсO91Lпр490Rэщ Xза O нL с3VщB"
|
17
|
+
# > Fixnum.∀
|
18
|
+
# # ⇒ 301
|
19
|
+
# > Hash.∀ size: 3
|
20
|
+
# # ⇒ {
|
21
|
+
# # "aenvxgmsuqhpxgsbhrcjvyvhlrbexa" => "ьюWB4IVачъитяCи жH3O 8илыP Dц Kх",
|
22
|
+
# # "awohozdxdjzvombswswsfzsqfqfguxc" => 202,
|
23
|
+
# # "befqyvqhmrncboilgdjwbqpyvfgtp" => "ифMцGSь фъ BубITмPэIHрTJлъ9OдJщ9"
|
24
|
+
# # }
|
25
|
+
# > Array.∀ size: 3
|
26
|
+
# # ⇒ [
|
27
|
+
# # [0] " эвъуL P 5июоCXъXе AB0 й1DьUфв",
|
28
|
+
# # [1] 800,
|
29
|
+
# # [2] 851
|
30
|
+
# # ]
|
31
|
+
# @author Alexei Matyushkin <am@mudasobwa.ru>
|
32
|
+
module MonkeyPatches
|
33
|
+
|
34
|
+
class ::Array
|
35
|
+
# @source def … ; block_given? ? each(&Proc.new) : each ; end
|
36
|
+
alias … each
|
37
|
+
# def ≠ ; block_given? ? reject(&Proc.new) : reject ; end
|
38
|
+
alias ≠ reject
|
39
|
+
# def ≡ ; block_given? ? select(&Proc.new) : select ; end
|
40
|
+
alias ≡ select
|
41
|
+
end
|
42
|
+
class ::Hash
|
43
|
+
# def … ; block_given? ? each(&Proc.new) : each ; end
|
44
|
+
alias … each
|
45
|
+
end
|
46
|
+
|
47
|
+
# Enchancement of +String+ class to generate random sample based on the pattern given.
|
48
|
+
class ::String
|
49
|
+
# Generates random sample of +String+.
|
50
|
+
# @example To create the string of length 12, consisting of lowercased latin letters:
|
51
|
+
# s1 = ('a'..'z').to_chars.∀ 12 # ⇒ fughtksnewqp
|
52
|
+
# s2 = "".∀(12, ('a'..'z')) # ⇒ jiuuoiqwbjty
|
53
|
+
# @see Range#to_chars
|
54
|
+
# @todo Possible wanna use {http://faker.rubyforge.org Faker} here
|
55
|
+
# @param size [Fixnum] the size of the sample to generate.
|
56
|
+
# @param symbols [Array<Char>] the list of characters used to generate the sample.
|
57
|
+
# @return [String] the string of the given length, consisting of random characters from the given set.
|
58
|
+
def ∀(size: 32, symbols: [*('A'..'Z'), *('а'..'я'), *('0'..'9'), *[' ']*10])
|
59
|
+
syms = case
|
60
|
+
when !self.empty? then self.scan('.')
|
61
|
+
when !(Array === symbols) && symbols.respond_to?(:to_a) then symbols.to_a
|
62
|
+
else symbols
|
63
|
+
end
|
64
|
+
raise ArgumentError.new("`:symbols` argument class must support `#sample` method (given #{symbols})") \
|
65
|
+
unless syms.respond_to? :sample
|
66
|
+
"".tap { |v| size.times { v << syms.sample } }.squeeze
|
67
|
+
end
|
68
|
+
alias any ∀
|
69
|
+
end
|
70
|
+
|
71
|
+
# Enchancement of +Fixnum+ class to generate random number in the interval [0, Fixnum).
|
72
|
+
class ::Fixnum
|
73
|
+
# Generates random +Fixnum+ in the given interval.
|
74
|
+
# @example To create the positive number not greater than 1000:
|
75
|
+
# num = 1000.∀ # ⇒ 634
|
76
|
+
# @return a random number in the given interval.
|
77
|
+
def ∀
|
78
|
+
rand(self)
|
79
|
+
end
|
80
|
+
alias any ∀
|
81
|
+
|
82
|
+
# Generates random +Fixnum+ in the given interval. We need +Fixnum+ implementing
|
83
|
+
# +new+ method for instantiating it as parameter in common way.
|
84
|
+
# @example To create the positive number not greater than 1000:
|
85
|
+
# num = Fixnum.new 1000 # ⇒ 48
|
86
|
+
def self.new val = 1024
|
87
|
+
val.∀
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Enchancement of +Array+ class to generate random array of the given size. The array elements
|
92
|
+
# are instances of the samples given. E. g. by default, there is an array of strings and fixnums
|
93
|
+
# produced.
|
94
|
+
class ::Array
|
95
|
+
# Generates random sample of +Array+.
|
96
|
+
# @example To create an array of three string elements:
|
97
|
+
# [""].∀ size: 3
|
98
|
+
# # ⇒ [
|
99
|
+
# # [0] " пт64AVAэеыGN еCйдчDLFUL еPTюQL ",
|
100
|
+
# # [1] "лW1O Cи 4TZ Yиз моBи2 AзмсU5г о ",
|
101
|
+
# # [2] "70ZIзQOMXC0нXLPMкGдлэY7Bщ7Eх ой4"
|
102
|
+
# # ]
|
103
|
+
# @param size [Fixnum] the size of the sample array to generate.
|
104
|
+
# @param samples [Array] the array of samples used to generate the sample array.
|
105
|
+
# @return [Array] the array of the given length, consisting of random elements from the given set.
|
106
|
+
def ∀(size: 64, samples: ["", 1000])
|
107
|
+
samples = self unless self.empty?
|
108
|
+
[].tap { |v| size.times { v << ::Kernel::random(:samples => samples) } }
|
109
|
+
end
|
110
|
+
alias any ∀
|
111
|
+
end
|
112
|
+
|
113
|
+
# Enchancement of +Hash+ class to generate random hash of the given size. The hash elements
|
114
|
+
# are instances of the samples given. The keys are in the range +(‘a’..‘z’)+.
|
115
|
+
# By default, there is a hash having strings and fixnums as values.
|
116
|
+
class ::Hash
|
117
|
+
# Generates random sample of +Hash+.
|
118
|
+
# @note When called on non-empty hash, the random elements are _added_ to the existing.
|
119
|
+
# @todo Do we really need to append randoms? Isn’t +{}.∀ | {:foo ⇒ 42}+ clearer?
|
120
|
+
# @example To create a hash of three elements:
|
121
|
+
# {}.∀ size: 3
|
122
|
+
# # ⇒ {
|
123
|
+
# # "pcnoljbhibgjywosztzheuimqfawzi" => 821,
|
124
|
+
# # "rjdrhidkhrowsonpsmaskdjfbhpuwunh" => " рлшеALя н нмкж0отDщ5 MеьFKB1Mъ5",
|
125
|
+
# # "zbalqtiqysdfbartnebvkmwzvudxkzmk" => "Dе904KшNщуO7EывхJбMUV йN Zч энж"
|
126
|
+
# # }
|
127
|
+
# @param size [Fixnum] the size of the sample hash to generate.
|
128
|
+
# @param samples [Array] the array of samples used to generate the values of the sample hash.
|
129
|
+
# @return [Hash] the hash of the given length, consisting of random elements from the given set.
|
130
|
+
def ∀(size: 64, samples: ["", 1000])
|
131
|
+
self.dup.tap { |v|
|
132
|
+
size.times {
|
133
|
+
v["".∀(:symbols => [*('a'..'z')])] = ::Kernel::random(:samples => samples)
|
134
|
+
}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
alias any ∀
|
138
|
+
end
|
139
|
+
|
140
|
+
# Enchancement of +Range+ class to join range elements into string.
|
141
|
+
class ::Range
|
142
|
+
# Joins range elements into string.
|
143
|
+
# @return [String] string representation of the range
|
144
|
+
def to_chars
|
145
|
+
self.to_a.join
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# @private
|
150
|
+
module ::Enumerable
|
151
|
+
def sum
|
152
|
+
self.inject(0){|accum, i| accum + i }
|
153
|
+
end
|
154
|
+
|
155
|
+
def mean
|
156
|
+
self.sum/self.length.to_f
|
157
|
+
end
|
158
|
+
|
159
|
+
def sample_variance
|
160
|
+
m = self.mean
|
161
|
+
sum = self.inject(0){|accum, i| accum +(i-m)**2 }
|
162
|
+
sum/(self.length - 1).to_f
|
163
|
+
end
|
164
|
+
|
165
|
+
def standard_deviation
|
166
|
+
return Math.sqrt(self.sample_variance)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Helper for parsing argument errors in machine-readable collection.
|
171
|
+
class ::ArgumentError
|
172
|
+
# Parses the error string and returns the machine-readable argument count contract.
|
173
|
+
# @return [Hash] consisting of an amount of arguments given,
|
174
|
+
# minimal required and (if makes sense) maximal.
|
175
|
+
def argument_data
|
176
|
+
# ⇒ wrong number of arguments (1 for 2..3)
|
177
|
+
/\((?<given>\d+)\s+\w+\s+(?<min_required>\d+)(?<modifier>\+|\.\.)?(?<max_required>\d+)\)/.match(self.to_s) { |m|
|
178
|
+
{ :given => m[:given],
|
179
|
+
:min_required => m[:min_required],
|
180
|
+
:max_required => case m[:modifier]
|
181
|
+
when '+' then '∞'
|
182
|
+
when '..' then h[:max_required]
|
183
|
+
else h[:min_required]
|
184
|
+
end
|
185
|
+
}
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Helper for parsing type errors in machine-readable format.
|
191
|
+
class ::TypeError
|
192
|
+
# Parses the error string and returns the machine-readable expected arguments classes.
|
193
|
+
# @return [Hash] consisting of two strings representing _given_ and _required_ argument types.
|
194
|
+
def type_data
|
195
|
+
# ⇒ can't convert Hash into Integer
|
196
|
+
# There are two ways to match: either rely on us locale, or find the uppercased classes
|
197
|
+
/[^[A-Z]]*(?<given>[A-Z]\w*)[^[A-Z]]*(?<required>[A-Z]\w*)/.match(self.to_s) { |m|
|
198
|
+
{ :given => m[:given], :required => m[:required] }
|
199
|
+
}
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Some aestetics in aliasing:
|
204
|
+
# @example
|
205
|
+
# my_proc = λ { |e| puts e } # ⇒ lambda, strict parameters list
|
206
|
+
# my_proc = Λ { |e| puts e } # ⇒ proc, not strict parameters list
|
207
|
+
module ::Kernel
|
208
|
+
# Alias for lambda
|
209
|
+
alias λ lambda
|
210
|
+
# Alias for proc
|
211
|
+
alias Λ proc
|
212
|
+
# default set of classes, supporting `random` feature
|
213
|
+
DEFAULT_SAMPLES ||= ["", 1024, {}, []].freeze
|
214
|
+
# @private
|
215
|
+
# The stub class for determining parameter list
|
216
|
+
class RandomVoid ; def ∀ ; NotImplementedError.new('RandomVoid class is not intended to use.') ; end ; end
|
217
|
+
|
218
|
+
protected
|
219
|
+
# Random instance of random class
|
220
|
+
# @param samples [Array] the instances of classes supporting +#random+ method.
|
221
|
+
# Those will vbe used as initial parameters for calls to `random` on them.
|
222
|
+
# @return [Object] random instance of one of the classes given as parameters
|
223
|
+
def random(samples: DEFAULT_SAMPLES)
|
224
|
+
samples.dup.sample.∀
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Helpers for calling methods and instantiate class silently, even if there are
|
229
|
+
# arguments to be passed to constructor/method. The main idea is to try to guess
|
230
|
+
# the parameters, awaited by method, generate randoms for them and finally call
|
231
|
+
# the method on the singleton instance of this class.
|
232
|
+
class ::Class
|
233
|
+
# Instance of a class, lazy initialized with guessed parameters. Cached.
|
234
|
+
# @note There is a possibility to explicitely set the singleton instance, in which case all the methods will be called on it.
|
235
|
+
# @todo Maybe we need to overwrite setter for this variable to avoid weird settings like +String.★ = Fixnum.new+
|
236
|
+
# @see ☎
|
237
|
+
attr_accessor :★
|
238
|
+
# The result of last call to method with with fake params. Cached.
|
239
|
+
# @see ☏
|
240
|
+
attr_reader :☆
|
241
|
+
|
242
|
+
# Tries to make a new instance of a class
|
243
|
+
def ☎
|
244
|
+
fake_parameters unless @★
|
245
|
+
@★
|
246
|
+
end
|
247
|
+
|
248
|
+
# Tries to call a method +m+ on a class.
|
249
|
+
# @param m [Symbol] the method to be called.
|
250
|
+
# @return [Object] the result of call to method +m+
|
251
|
+
def ☏ m = :to_s
|
252
|
+
☎.send(m, *fake_parameters(:m => m))
|
253
|
+
end
|
254
|
+
|
255
|
+
# Instantiates the class with applicable random value.
|
256
|
+
# @param args [Array] if passed, used as sceleton for a call to {#∀} method.
|
257
|
+
# @return [Instance] a random value of this Class class.
|
258
|
+
def ∀ *args
|
259
|
+
begin
|
260
|
+
inst = self.☎
|
261
|
+
raise NotImplementedError.new("The class should implement `∀` instance method") \
|
262
|
+
unless inst.respond_to? :∀
|
263
|
+
inst.∀ *args
|
264
|
+
rescue Exception => e
|
265
|
+
raise NotImplementedError.new("No way: #{e}")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
# First of all, let′s try to determine parameters needed:
|
271
|
+
#
|
272
|
+
# method(__method__).parameters.inject([]) { |res, v| (res << v[1]) if v[0] == :req; res }
|
273
|
+
# method(__method__).parameters.select { |a| a[0] == :req }.map { |a| a[1] }
|
274
|
+
#
|
275
|
+
# Rehearsal ----------------------------------------------
|
276
|
+
# inject 0.510000 0.000000 0.510000 ( 0.507843)
|
277
|
+
# select+map 0.470000 0.000000 0.470000 ( 0.472166)
|
278
|
+
# ------------------------------------- total: 0.980000sec
|
279
|
+
# user system total real
|
280
|
+
# inject 0.520000 0.000000 0.520000 ( 0.518626)
|
281
|
+
# select+map 0.470000 0.000000 0.470000 ( 0.473081)
|
282
|
+
#
|
283
|
+
# That’s why we are to use `select+map` version.
|
284
|
+
def required_parameters(m: :initialize)
|
285
|
+
param_selector = λ{ |type|
|
286
|
+
self.instance_method(m).parameters.select { |p| p[0] == type }.map { |p| p[1] }
|
287
|
+
}
|
288
|
+
{ :req => param_selector.call(:req), :rest => param_selector.call(:rest) }
|
289
|
+
end
|
290
|
+
# Suggests random parameters for instance method of a class
|
291
|
+
# Usage: `String.fake_parameters :method`
|
292
|
+
# @param m [Symbol] the method to suggest parameters for
|
293
|
+
# @return [Array] an array of parameters suggested
|
294
|
+
def fake_parameters(m: nil)
|
295
|
+
if (@☆ ||= {})[m].nil?
|
296
|
+
# We need an instance first of all
|
297
|
+
if m.nil? || !@★
|
298
|
+
params = required_parameters
|
299
|
+
guessed = [].∀(:size => params[:req].size, :samples => [RandomVoid.new])
|
300
|
+
guessed.map! { |elem|
|
301
|
+
begin
|
302
|
+
elem if @★ ||= self.new(*guessed)
|
303
|
+
rescue TypeError => e
|
304
|
+
::Kernel.const_get(e.type_data[:required]).new.∀
|
305
|
+
end
|
306
|
+
}
|
307
|
+
@★ ||= self.new(*guessed)
|
308
|
+
@☆[nil] = guessed.map(&:class)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Let’s proceed with method
|
312
|
+
unless m.nil?
|
313
|
+
params = required_parameters :m => m
|
314
|
+
|
315
|
+
guessed = [].∀(:size => params[:req].size, :samples => [RandomVoid.new])
|
316
|
+
guessed.map! { |elem|
|
317
|
+
begin
|
318
|
+
elem if @☆=@★.send(m, *guessed)
|
319
|
+
rescue TypeError => e
|
320
|
+
::Kernel.const_get(e.type_data[:required]).new.∀
|
321
|
+
end
|
322
|
+
}
|
323
|
+
@☆[m] = guessed.map(&:class)
|
324
|
+
end
|
325
|
+
|
326
|
+
guessed
|
327
|
+
else
|
328
|
+
@☆[m].map(&:∀)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../dsl/bm_dsl'
|
4
|
+
|
5
|
+
# FIXME Probably, that class is to be made as Proxy. Then during the methods
|
6
|
+
# processing we’ll have an access to it to print the benchmarks out
|
7
|
+
# within method scope…
|
8
|
+
module YARD
|
9
|
+
module Handlers
|
10
|
+
# class BenchmarkObject < YARD::CodeObjects::Base
|
11
|
+
# def type ; :benchmark ; end
|
12
|
+
# def sep ; '%' ; end
|
13
|
+
# end
|
14
|
+
|
15
|
+
class BenchmarkHandler < YARD::Handlers::Ruby::DSLHandler
|
16
|
+
handles method_call(:benchmark)
|
17
|
+
handles method_call(:⌚)
|
18
|
+
# we should only match method calls inside a namespace (class or module), not inside a method
|
19
|
+
# namespace_only
|
20
|
+
|
21
|
+
def process
|
22
|
+
cos = []
|
23
|
+
statement.parameters.each { |astnode|
|
24
|
+
if astnode.respond_to? :jump
|
25
|
+
m = "#{astnode.jump(:string_content).source[1..-1]}" # [1..-1] is to get rid of symbol’s colon
|
26
|
+
if res = YARD::Bench::Marks.get("#{statement.file}", "#{namespace}", "#{m}")
|
27
|
+
obj = YARD::CodeObjects::MethodObject.new(namespace, "#{m}")
|
28
|
+
obj.benchmarks = res.map { |e| e.times }.flatten
|
29
|
+
obj.power = res.map { |e| e.power }.flatten[0]
|
30
|
+
obj.deviation = res.map { |e| e.deviation }.flatten[0]
|
31
|
+
obj.memory = res.map { |e| e.memory }.flatten
|
32
|
+
cos << obj
|
33
|
+
# bmo = BenchmarkObject.new(namespace, m)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
}
|
37
|
+
cos
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_file(file)
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/yard-bench.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
YARD::Templates::Engine.register_template_path File.dirname(__FILE__) + '/../templates'
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'yard-bench', 'version')
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), 'dsl', 'monkeypatches')
|
6
|
+
require File.join(File.dirname(__FILE__), 'dsl', 'bm_dsl')
|
7
|
+
|
8
|
+
require File.join(File.dirname(__FILE__), 'yard-bench', 'handler')
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'yard-bench'
|
4
|
+
|
5
|
+
# Requires supporting files with custom matchers and macros, etc,
|
6
|
+
# in ./support/ and its subdirectories.
|
7
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% if object[:benchmarks] %>
|
2
|
+
<div class="tags">
|
3
|
+
<h4>Benchmarks:</h4>
|
4
|
+
<ul class="benchmarks">
|
5
|
+
<% for bm in object[:benchmarks] %>
|
6
|
+
<% key = "#{bm.keys[0]}".length - 1 %>
|
7
|
+
<% val = "%.3f" % bm.values[0] %>
|
8
|
+
<li><span class="name">×10<sup><%= key %></sup></span> ⇒ <%= val %>
|
9
|
+
</li>
|
10
|
+
<% end %>
|
11
|
+
</ul>
|
12
|
+
<% dev = '%.2f' % object[:deviation] %>
|
13
|
+
<% pow = object[:power].nil? ? "unknown" : "#{'%.3f' % object[:power]}×10<sup>-6</sup>×O(N)" %>
|
14
|
+
<h5>Deviation: <span class="name"><%= dev %>%</span> Power: <span class="name"><%= pow %></span></h5>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
<% if object[:benchmarks] %>
|
4
|
+
Benchmarks:
|
5
|
+
-----------
|
6
|
+
<% for bm in object[:benchmarks] %>
|
7
|
+
<% key = "#{bm.keys[0]}".length - 1 %>
|
8
|
+
<% val = "%.2f" % bm.values[0] %>
|
9
|
+
<%= indent wrap("→ ×10^#{key} ⇒ #{val}" ) %>
|
10
|
+
<% end %>
|
11
|
+
<% dev = "%.2f" % object[:deviation] %>
|
12
|
+
<% pow = object[:power].nil? ? "unknown" : object[:power] %>
|
13
|
+
<%= indent wrap("→ Deviation: #{dev}" ) %>
|
14
|
+
<%= indent wrap("→ Power: #{pow}" ) %>
|
15
|
+
<% end %>
|
data/yard-bench.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'yard-bench/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'yard-bench'
|
6
|
+
s.version = YARD::Bench::VERSION
|
7
|
+
s.license = 'MIT'
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.date = '2013-02-21'
|
10
|
+
s.authors = ['Alexei Matyushkin']
|
11
|
+
s.email = 'am@mudasobwa.ru'
|
12
|
+
s.homepage = 'http://github.com/mudasobwa/yard-bench'
|
13
|
+
s.summary = %Q{Add a benchmark functionality to Yard.}
|
14
|
+
s.description = %Q{YARD plugin, which adds a benchmarking results to YARDoc}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
'LICENSE',
|
17
|
+
'README.md',
|
18
|
+
]
|
19
|
+
|
20
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7')
|
21
|
+
s.rubygems_version = '1.3.7'
|
22
|
+
s.specification_version = 3
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
27
|
+
s.require_paths = ['lib']
|
28
|
+
|
29
|
+
s.add_development_dependency 'rspec'
|
30
|
+
s.add_development_dependency 'cucumber'
|
31
|
+
s.add_development_dependency 'bueller'
|
32
|
+
s.add_development_dependency 'yard'
|
33
|
+
s.add_development_dependency 'yard-cucumber'
|
34
|
+
s.add_development_dependency 'redcarpet'
|
35
|
+
end
|
36
|
+
|