big-o 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/Gemfile +7 -0
- data/LICENSE +19 -0
- data/README.md +88 -0
- data/big-o.gemspec +15 -0
- data/lib/big-o-matchers.rb +1 -0
- data/lib/big-o.rb +7 -0
- data/lib/big-o/complexity_base.rb +128 -0
- data/lib/big-o/exceptions.rb +21 -0
- data/lib/big-o/matchers/match_complexity_level.rb +32 -0
- data/lib/big-o/space_complexity.rb +38 -0
- data/lib/big-o/time_complexity.rb +32 -0
- data/spec/big-o/complexity_base_spec.rb +58 -0
- data/spec/big-o/space_complexity_spec.rb +57 -0
- data/spec/big-o/time_complexity_spec.rb +71 -0
- data/spec/helpers/simulation.rb +18 -0
- data/spec/matchers/match_complexity_level_spec.rb +46 -0
- data/spec/spec_helper.rb +8 -0
- metadata +87 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Vincent Bonmalais
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is furnished
|
8
|
+
to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Big-O
|
2
|
+
|
3
|
+
Big-O is a gem which analyse an anonymous function and verify that it follow a specific pattern
|
4
|
+
in its memory usage or its execution time.
|
5
|
+
|
6
|
+
## Requirements
|
7
|
+
|
8
|
+
Calculation of memory space is done via `ps`, and therefore, doesn't work on Windows.
|
9
|
+
|
10
|
+
This gem has been tested on Mac OS X, using Ruby 1.9.3.
|
11
|
+
|
12
|
+
## Install
|
13
|
+
|
14
|
+
Installation is made through RubyGem:
|
15
|
+
|
16
|
+
```
|
17
|
+
gem install big-o
|
18
|
+
```
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Directly in your code
|
23
|
+
|
24
|
+
Checking if a function has a complexity of O(n) is as simple as this:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'big-o'
|
28
|
+
|
29
|
+
time_complexity = BigO::TimeComplexity({
|
30
|
+
:fn => lambda { |n| do_something_time_consuming(n) },
|
31
|
+
:level => lambda { |n| n }
|
32
|
+
})
|
33
|
+
time_complexity.process # => true if it is growing in time constantly.
|
34
|
+
```
|
35
|
+
|
36
|
+
It is also possible to define multiple configuration parameters:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'big-o'
|
40
|
+
|
41
|
+
space_complexity = BigO::SpaceComplexity({
|
42
|
+
:fn => lambda { |n| do_something_space_consuming(n) }
|
43
|
+
:level => lambda { |n| n },
|
44
|
+
:range => 1..20, # value of n
|
45
|
+
:timeout => 10, # time in seconds
|
46
|
+
:approximation => 0.05, # percentage
|
47
|
+
:error_pct => 0.05, # percentage
|
48
|
+
:minimum_result_set_size => 3 # minimum results
|
49
|
+
})
|
50
|
+
```
|
51
|
+
|
52
|
+
### RSpec Matchers
|
53
|
+
|
54
|
+
If you are using RSpec, there is a matcher already defined for matching a complexity level:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
require 'big-o'
|
58
|
+
require 'big-o-matchers'
|
59
|
+
|
60
|
+
describe 'do_something_time_consuming' do
|
61
|
+
before :each do
|
62
|
+
@time_complexity = BigO::TimeComplexity({
|
63
|
+
:fn => lambda { |n| do_something_time_consuming(n) }
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should have a complexity of O(n)' do
|
68
|
+
@time_complexity.should match_complexity_level 'O(n)', lambda { |n| n }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should not have a complexity of O(1)' do
|
72
|
+
@time_complexity.should match_complexity_level 'O(1)', lambda { |_| 1 }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
The string used as the first parameter (e.g. `'O(n)'`) is used to describe the lambda given as the
|
78
|
+
second parameter.
|
79
|
+
|
80
|
+
## Reference
|
81
|
+
|
82
|
+
You may want to read more on the subject by reading:
|
83
|
+
* http://en.wikipedia.org/wiki/Analysis_of_algorithms
|
84
|
+
* http://en.wikipedia.org/wiki/Big_O_notation
|
85
|
+
|
86
|
+
## License
|
87
|
+
|
88
|
+
Complexity is licensed under the MIT License - see the LICENSE file for details
|
data/big-o.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'big-o'
|
3
|
+
s.version = '0.1'
|
4
|
+
s.date = '2012-07-25'
|
5
|
+
s.summary = "Calculate function complexity (using Big O notation)"
|
6
|
+
s.description = "Big-O is a gem which analyse an anonymous function and verify that it " +
|
7
|
+
"follow a specific pattern in its memory usage or its execution time."
|
8
|
+
s.authors = ["Vincent Bonmalais"]
|
9
|
+
s.email = 'vbonmalais@gmail.com'
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.test_files = `git ls-files -- spec`.split("\n")
|
12
|
+
s.homepage = 'https://github.com/kouno/big-o'
|
13
|
+
|
14
|
+
s.add_development_dependency('rspec', '~>2.10.0')
|
15
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'big-o/matchers/match_complexity_level'
|
data/lib/big-o.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module BigO
|
2
|
+
# ComplexityBase regroup every process common to the benchmarking of a function.
|
3
|
+
module ComplexityBase
|
4
|
+
|
5
|
+
# @return [Boolean] Result from measurement
|
6
|
+
attr_accessor :result
|
7
|
+
|
8
|
+
# @return [Hash] Contains the whole benchmark
|
9
|
+
attr_accessor :result_set
|
10
|
+
|
11
|
+
# @return [Float] Element on which the whole benchmark is based (for <code>n = 1</code>)
|
12
|
+
attr_accessor :scale
|
13
|
+
|
14
|
+
# @return [Hash] Contains the different configurable options
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
# Configures default values.
|
18
|
+
#
|
19
|
+
# @param [Hash] options the options necessary to benchmark the given lambda. Any of these options
|
20
|
+
# can be defined just before running a simulation using <code>options</code>.
|
21
|
+
#
|
22
|
+
# {
|
23
|
+
# :fn => lambda { |n| do_something(n) } # function which will be measured [required]
|
24
|
+
# :level => lambda { |n| n } # complexity of the function [required]
|
25
|
+
# :range => 1..20, # values of `n` for which it will run the function
|
26
|
+
# :timeout => 10, # time (in seconds) after which the simulation will stop running
|
27
|
+
# :approximation => 0.05, # percentage by which we approximate our expected results
|
28
|
+
# :error_pct => 0.05, # percentage of times where we allow errors due to concurrent processing
|
29
|
+
# :minimum_result_set_size => 3 # minimum number of results we need to have consistent data
|
30
|
+
# }
|
31
|
+
# @return [void]
|
32
|
+
def initialize(options = {})
|
33
|
+
@options = { :range => 1..20,
|
34
|
+
:timeout => 10,
|
35
|
+
:approximation => 0.05,
|
36
|
+
:error_pct => 0.05,
|
37
|
+
:minimum_result_set_size => 3 }
|
38
|
+
|
39
|
+
@options.merge!(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Benchmarks the given function (<code>@fn</code>) and tells if it follows the given pattern
|
43
|
+
# (<code>@options[:level]</code>).
|
44
|
+
#
|
45
|
+
# @return [Boolean]
|
46
|
+
def process
|
47
|
+
@scale ||= get_scale
|
48
|
+
examine_result_set(*run_simulation)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Runs simulation.
|
52
|
+
#
|
53
|
+
# A simulation can fail to execute every element in <code>range</code> because it exceeded the timeout limit. If no result
|
54
|
+
# was returned in the `timeout` time frame, this method will generate an exception.
|
55
|
+
#
|
56
|
+
# @return [Array] contains an array (first element) of values which are the measurement of the function
|
57
|
+
# and another array (second element) of values which are the expected values for the first one.
|
58
|
+
# @raise [Timeout::Error]
|
59
|
+
def run_simulation
|
60
|
+
real_complexity = {}
|
61
|
+
expected_complexity = {}
|
62
|
+
|
63
|
+
begin
|
64
|
+
Timeout::timeout(@options[:timeout]) do
|
65
|
+
@options[:range].each do |n|
|
66
|
+
next if (indicator = measure(n, &@options[:fn])) == 0
|
67
|
+
real_complexity[n] = indicator
|
68
|
+
expected_complexity[n] = @options[:level].call(n)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
rescue Timeout::Error => e
|
72
|
+
if real_complexity.empty? || expected_complexity.empty?
|
73
|
+
raise e
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
@result_set = real_complexity
|
78
|
+
[real_complexity, expected_complexity]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Parses data from <code>#run_simulation</code>.
|
82
|
+
#
|
83
|
+
# <code>examine_result_set</code> will return true only if these conditions are met:
|
84
|
+
# - expected complexity is never exceeded. Some inconsistencies may however be allowed
|
85
|
+
# (see <code>@options[:error_pct]</code>).
|
86
|
+
# - there is a minimum of X results in real_complexity, where X is <code>@options[:minimum_result_set_size]</code>.
|
87
|
+
# if this condition is not met, an exception will be thrown.
|
88
|
+
#
|
89
|
+
# @param [Array] real_complexity
|
90
|
+
# @param [Array] expected_complexity
|
91
|
+
# @return [Boolean]
|
92
|
+
# @raise [SmallResultSetError]
|
93
|
+
def examine_result_set(real_complexity, expected_complexity)
|
94
|
+
if real_complexity.size <= @options[:minimum_result_set_size]
|
95
|
+
raise SmallResultSetError.new(@options[:minimum_result_set_size])
|
96
|
+
end
|
97
|
+
|
98
|
+
allowed_inconsistencies = (expected_complexity.size * @options[:error_pct]).floor
|
99
|
+
expected_complexity.each do |n, level|
|
100
|
+
next if n == 1
|
101
|
+
estimated_complexity = @scale * level
|
102
|
+
estimated_complexity += estimated_complexity * @options[:approximation]
|
103
|
+
if estimated_complexity <= real_complexity[n]
|
104
|
+
if allowed_inconsistencies > 0
|
105
|
+
allowed_inconsistencies -= 1
|
106
|
+
next
|
107
|
+
end
|
108
|
+
@result = false
|
109
|
+
break
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
@result = true if @result.nil?
|
114
|
+
@result
|
115
|
+
end
|
116
|
+
|
117
|
+
# Finds what is the first measure of our function.
|
118
|
+
#
|
119
|
+
# @return [Float]
|
120
|
+
def get_scale
|
121
|
+
runs = []
|
122
|
+
10.times do
|
123
|
+
runs << measure(1, &@options[:fn])
|
124
|
+
end
|
125
|
+
runs.inject(:+) / 10
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module BigO
|
2
|
+
# The function could not be run more than X times (where X is a configurable value).
|
3
|
+
#
|
4
|
+
# This exception happens if the function is too slow to run, or if the timeout is too short.
|
5
|
+
class SmallResultSetError < StandardError
|
6
|
+
# @param [Integer] resultset_size number of values the result set contains.
|
7
|
+
def initialize(resultset_size)
|
8
|
+
super "Less than #{resultset_size} values could be retrieved." +
|
9
|
+
" Try using longer timeout or a different range. (function complexity may be too high)"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# The function runs too fast and can't be quantified by our measurement tool.
|
14
|
+
#
|
15
|
+
# To fix this error, it is possible to augment the number of times the function should run.
|
16
|
+
class InstantaneousExecutionError < StandardError
|
17
|
+
def initialize
|
18
|
+
super "Function execution time can't be quantified. (execution speed close to instantaneous)"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec::Matchers.define :match_complexity_level do |o_notation, complexity_level|
|
2
|
+
match do |complexity|
|
3
|
+
complexity.options[:level] = complexity_level
|
4
|
+
complexity.process
|
5
|
+
end
|
6
|
+
|
7
|
+
failure_message_for_should do |complexity|
|
8
|
+
# since we are playing around with floating values,
|
9
|
+
# sorting the result_set helps minimising issues with addition of floats
|
10
|
+
result_set = complexity.result_set.sort
|
11
|
+
total = result_set.inject(0) { |sum, r| sum + r[1] }.to_f
|
12
|
+
"expected a complexity level of #{o_notation}, " +
|
13
|
+
"got scale: #{complexity.scale} min: #{result_set[0][1]} " +
|
14
|
+
"max: #{result_set[-1][1]} " +
|
15
|
+
"avg: #{total / result_set.size} " +
|
16
|
+
"total values: #{result_set.size} on #{complexity.options[:range]}"
|
17
|
+
end
|
18
|
+
|
19
|
+
failure_message_for_should_not do |complexity|
|
20
|
+
result_set = complexity.result_set.sort
|
21
|
+
total = result_set.inject(0) { |sum, r| sum + r[1] }.to_f
|
22
|
+
"expected a complexity level over #{o_notation}, " +
|
23
|
+
"got scale: #{complexity.scale} min: #{result_set[0][1]} " +
|
24
|
+
"max: #{result_set[-1][1]} " +
|
25
|
+
"avg: #{total / result_set.size} " +
|
26
|
+
"total values: #{result_set.size} on #{complexity.options[:range]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
description do
|
30
|
+
"should match complexity level #{o_notation}"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module BigO
|
2
|
+
# Measure space complexity.
|
3
|
+
class SpaceComplexity
|
4
|
+
include ComplexityBase
|
5
|
+
|
6
|
+
# Measures the memory space that <code>fn</code> is using.
|
7
|
+
#
|
8
|
+
# @param [Array] args arguments which the given block should take
|
9
|
+
# @yield function which should be measured (fn)
|
10
|
+
# @return [Float] measurement
|
11
|
+
def measure(*args, &b)
|
12
|
+
memory_measures = []
|
13
|
+
GC.disable
|
14
|
+
|
15
|
+
pid = Process.fork do
|
16
|
+
memory_measures << `ps -o rss= -p #{Process.pid}`.to_i
|
17
|
+
b.call(*args)
|
18
|
+
memory_measures << `ps -o rss= -p #{Process.pid}`.to_i
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
while (memory_indicator = `ps -o rss= -p #{pid}`.to_i)
|
23
|
+
break if memory_indicator == 0
|
24
|
+
memory_measures << memory_indicator
|
25
|
+
end
|
26
|
+
|
27
|
+
Process.wait
|
28
|
+
|
29
|
+
GC.enable
|
30
|
+
|
31
|
+
if memory_measures.size > 2
|
32
|
+
memory_measures.max - memory_measures.min
|
33
|
+
else
|
34
|
+
0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BigO
|
2
|
+
# Measure time complexity.
|
3
|
+
class TimeComplexity
|
4
|
+
include ComplexityBase
|
5
|
+
|
6
|
+
# Raises the error percentage due to possible concurrency issue on the system (which in
|
7
|
+
# certain cases may cause some measures to be far longer than others).
|
8
|
+
def initialize(options = {})
|
9
|
+
options = { :error_pct => 0.1 }.merge(options)
|
10
|
+
super(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Checks if the function can be measured and throw an error if it could not.
|
14
|
+
def process
|
15
|
+
@scale ||= get_scale
|
16
|
+
raise InstantaneousExecutionError.new unless @scale > 0
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
# Measures the execution time that <code>fn</code> is using.
|
21
|
+
#
|
22
|
+
# @param [Array] args arguments which the given block should take
|
23
|
+
# @yield function which should be measured (fn)
|
24
|
+
# @return [Float] measurement
|
25
|
+
def measure(*args, &b)
|
26
|
+
t0 = Process.times
|
27
|
+
b.call(*args)
|
28
|
+
t1 = Process.times
|
29
|
+
t1.utime - t0.utime
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include BigO
|
3
|
+
|
4
|
+
describe ComplexityBase do
|
5
|
+
before :all do
|
6
|
+
class FunctionComplexity
|
7
|
+
include ComplexityBase
|
8
|
+
|
9
|
+
def measure
|
10
|
+
1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'with default values' do
|
16
|
+
before :each do
|
17
|
+
@fn_complexity = FunctionComplexity.new
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have a timeout of 10 seconds' do
|
21
|
+
@fn_complexity.options[:timeout].should == 10
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should have no result defined' do
|
25
|
+
@fn_complexity.result.should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should have an approximation of 5%' do
|
29
|
+
@fn_complexity.options[:approximation].should == 0.05
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should refuse to process less than 4 values when processing a function' do
|
33
|
+
real_complexity = {}
|
34
|
+
expected_complexity = {}
|
35
|
+
3.times do |i|
|
36
|
+
real_complexity[i] = i
|
37
|
+
expected_complexity[i] = i
|
38
|
+
end
|
39
|
+
|
40
|
+
@fn_complexity.scale = 1
|
41
|
+
lambda {
|
42
|
+
@fn_complexity.examine_result_set(real_complexity, expected_complexity)
|
43
|
+
}.should raise_error(SmallResultSetError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with overwritten values' do
|
48
|
+
before :each do
|
49
|
+
@fn_complexity = FunctionComplexity.new({ :timeout => 1,
|
50
|
+
:approximation => 0.1 })
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should have the configured values' do
|
54
|
+
@fn_complexity.options[:timeout].should == 1
|
55
|
+
@fn_complexity.options[:approximation].should == 0.1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include BigO
|
3
|
+
|
4
|
+
describe SpaceComplexity do
|
5
|
+
before :each do
|
6
|
+
@space_complexity = SpaceComplexity.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should raise an exception if timeout is reached and no result was found' do
|
10
|
+
@space_complexity.options[:timeout] = 0.001
|
11
|
+
@space_complexity.options[:fn] = lambda { |_| simulate_memory_space(1) }
|
12
|
+
lambda { @space_complexity.process }.should raise_error(Timeout::Error)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#process on complexity O(1)' do
|
16
|
+
before :each do
|
17
|
+
@space_complexity.options[:fn] = lambda { |_| simulate_memory_space(1024) }
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should run the function and produce a report when time threshold is hit' do
|
21
|
+
@space_complexity.options[:approximation] = 0.2
|
22
|
+
@space_complexity.should match_complexity_level 'O(1)', lambda { |_| 1 }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#process on complexity O(n)' do
|
27
|
+
before :each do
|
28
|
+
@space_complexity.options[:fn] = lambda { |n| simulate_memory_space(1024 * n) }
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should run the function and produce a report when time threshold is hit' do
|
32
|
+
@space_complexity.should match_complexity_level 'O(n)', lambda { |n| n }
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should return false if the complexity does not match (too low)' do
|
36
|
+
@space_complexity.should_not match_complexity_level 'O(1)', lambda { |_| 1 }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#process on complexity O(n**2)' do
|
42
|
+
before :each do
|
43
|
+
@space_complexity.options[:fn] = lambda { |n| simulate_memory_space(1024 * n**2) }
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should run the function and produce a report when time threshold is hit' do
|
47
|
+
@space_complexity.should match_complexity_level 'O(n^2)', lambda { |n| n**2 }
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should return false if the complexity does not match (too low)' do
|
51
|
+
@space_complexity.should_not match_complexity_level 'O(n)', lambda { |n| n }
|
52
|
+
|
53
|
+
@space_complexity.options[:approximation] = 0.2
|
54
|
+
@space_complexity.should_not match_complexity_level 'O(1)', lambda { |_| 1 }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include BigO
|
3
|
+
|
4
|
+
describe TimeComplexity do
|
5
|
+
before :each do
|
6
|
+
@time_complexity = TimeComplexity.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should raise an exception if timeout is reached and no result was found' do
|
10
|
+
@time_complexity.options[:timeout] = 0.001
|
11
|
+
@time_complexity.options[:fn] = proc { simulate_utime_processing(1) }
|
12
|
+
lambda { @time_complexity.process }.should raise_error(Timeout::Error)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#process on complexity O(1)' do
|
16
|
+
before :each do
|
17
|
+
@time_complexity.options[:fn] = lambda { |_| simulate_utime_processing(0.5) }
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should run the function and produce a report when time threshold is hit' do
|
21
|
+
@time_complexity.options[:approximation] = 0.2
|
22
|
+
@time_complexity.should match_complexity_level 'O(1)', lambda { |_| 1 }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#process on complexity O(n)' do
|
27
|
+
before :each do
|
28
|
+
@time_complexity.options[:fn] = lambda { |n| simulate_utime_processing(0.5 * n) }
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should run the function and produce a report when time threshold is hit' do
|
32
|
+
@time_complexity.should match_complexity_level 'O(n)', lambda { |n| n }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#process on complexity O(n**2)' do
|
37
|
+
before :each do
|
38
|
+
@time_complexity.options[:minimum_result_set_size] = 0
|
39
|
+
@time_complexity.options[:fn] = lambda { |n| simulate_utime_processing(0.5 * (n**2)) }
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should run the function and produce a report when time threshold is hit' do
|
43
|
+
@time_complexity.should match_complexity_level 'O(n^2)', lambda { |n| n**2 }
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should return false if the complexity does not match (too low)' do
|
47
|
+
@time_complexity.options[:approximation] = 0.2
|
48
|
+
@time_complexity.should_not match_complexity_level 'O(1)', lambda { |_| 1 }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'very small execution time functions (0.1 second and below)' do
|
53
|
+
it 'should still be valid in case of O(n**2)' do
|
54
|
+
@time_complexity.options[:fn] = lambda { |n| simulate_utime_processing(0.01 * n**2) }
|
55
|
+
@time_complexity.should match_complexity_level 'O(n**2)', lambda { |n| n**2 }
|
56
|
+
@time_complexity.should_not match_complexity_level 'O(n log(n))', lambda { |n| n * Math::log(n) }
|
57
|
+
@time_complexity.should_not match_complexity_level 'O(1)', lambda { |_| 1 }
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should still be valid in case of O(n)' do
|
61
|
+
@time_complexity.options[:fn] = lambda { |n| simulate_utime_processing(0.01 * n) }
|
62
|
+
@time_complexity.should match_complexity_level 'O(n)', lambda { |n| n }
|
63
|
+
@time_complexity.should_not match_complexity_level 'O(1)', lambda { |_| 1 }
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should throw an error if execution time is not measurable for n = 1 (execution time close to ~0.001 second)' do
|
67
|
+
@time_complexity.options[:fn] = lambda { |n| 0.001 * n**2 }
|
68
|
+
lambda { @time_complexity.process }.should raise_error(InstantaneousExecutionError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module BigO
|
2
|
+
module Helpers
|
3
|
+
ONE_KILO_OCTET = 'a' * 1024
|
4
|
+
|
5
|
+
def simulate_utime_processing(seconds)
|
6
|
+
t0 = Process.times
|
7
|
+
begin
|
8
|
+
t1 = Process.times
|
9
|
+
end while (t1.utime - t0.utime) < seconds
|
10
|
+
end
|
11
|
+
|
12
|
+
def simulate_memory_space(ko)
|
13
|
+
space = ONE_KILO_OCTET * ko
|
14
|
+
sleep(0.01)
|
15
|
+
space
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include BigO
|
3
|
+
|
4
|
+
describe 'match_complexity_level matcher' do
|
5
|
+
before :each do
|
6
|
+
@test_complexity = SpaceComplexity.new({ :fn => lambda { |n| simulate_memory_space(1024 * n) } })
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should not match O(1) for a constant augmentation" do
|
10
|
+
@matcher = match_complexity_level('O(1)', lambda { |_| 1 })
|
11
|
+
@matcher.matches?(@test_complexity).should be_false
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should match O(n) for a constant augmentation" do
|
15
|
+
@matcher = match_complexity_level('O(n)', lambda { |n| n })
|
16
|
+
@matcher.matches?(@test_complexity).should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when using should' do
|
20
|
+
before :each do
|
21
|
+
@matcher = match_complexity_level('O(1)', lambda { |_| 1 })
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should provide a descriptive error message" do
|
25
|
+
@matcher.matches?(@test_complexity)
|
26
|
+
@matcher.failure_message_for_should.should =~ /\Aexpected a complexity level of O\(1\)/
|
27
|
+
@matcher.failure_message_for_should.should =~ /got scale: [0-9.]+ min: [0-9.]+/
|
28
|
+
@matcher.failure_message_for_should.should =~ /max: [0-9.]+ avg: [0-9.]+/
|
29
|
+
@matcher.failure_message_for_should.should =~ /total values: [0-9]+ on 1\.\.20\Z/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when using should_not' do
|
34
|
+
before :each do
|
35
|
+
@matcher = match_complexity_level('O(n)', lambda { |n| n })
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should provide a descriptive error message" do
|
39
|
+
@matcher.matches?(@test_complexity)
|
40
|
+
@matcher.failure_message_for_should_not.should =~ /\Aexpected a complexity level over O\(n\)/
|
41
|
+
@matcher.failure_message_for_should_not.should =~ /got scale: [0-9.]+ min: [0-9.]+/
|
42
|
+
@matcher.failure_message_for_should_not.should =~ /max: [0-9.]+ avg: [0-9.]+/
|
43
|
+
@matcher.failure_message_for_should_not.should =~ /total values: [0-9]+ on 1\.\.20\Z/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: big-o
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vincent Bonmalais
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.10.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.10.0
|
30
|
+
description: Big-O is a gem which analyse an anonymous function and verify that it
|
31
|
+
follow a specific pattern in its memory usage or its execution time.
|
32
|
+
email: vbonmalais@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- .rspec
|
39
|
+
- Gemfile
|
40
|
+
- LICENSE
|
41
|
+
- README.md
|
42
|
+
- big-o.gemspec
|
43
|
+
- lib/big-o-matchers.rb
|
44
|
+
- lib/big-o.rb
|
45
|
+
- lib/big-o/complexity_base.rb
|
46
|
+
- lib/big-o/exceptions.rb
|
47
|
+
- lib/big-o/matchers/match_complexity_level.rb
|
48
|
+
- lib/big-o/space_complexity.rb
|
49
|
+
- lib/big-o/time_complexity.rb
|
50
|
+
- spec/big-o/complexity_base_spec.rb
|
51
|
+
- spec/big-o/space_complexity_spec.rb
|
52
|
+
- spec/big-o/time_complexity_spec.rb
|
53
|
+
- spec/helpers/simulation.rb
|
54
|
+
- spec/matchers/match_complexity_level_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
homepage: https://github.com/kouno/big-o
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.23
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Calculate function complexity (using Big O notation)
|
80
|
+
test_files:
|
81
|
+
- spec/big-o/complexity_base_spec.rb
|
82
|
+
- spec/big-o/space_complexity_spec.rb
|
83
|
+
- spec/big-o/time_complexity_spec.rb
|
84
|
+
- spec/helpers/simulation.rb
|
85
|
+
- spec/matchers/match_complexity_level_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
has_rdoc:
|