big-o 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.
- 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:
|