ocg 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8e9901f2790c6f74257fc4a081df5bbacefb6084fcbd5f1e31c5bf1cd5e69b7f
4
+ data.tar.gz: 602a5952e6ece5235fd19b469113903cf4a3750043aa35fdfe5091cb865d4903
5
+ SHA512:
6
+ metadata.gz: 683e94067c89e54ffa979c682d40ef6739c8c3a4d92be87a6250cde5c6981498dde7dc42adddb1f78561303954b4344ca7642b15693c616920e6313f589fcb85
7
+ data.tar.gz: 10a4f52d299916f8237ccdfa65258a261d1b49811700fc7d73813575a36547aafed3d4ae96686445cc63936b88db90cc3eae7547da335c1589984df9da82fc30
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Andrew Aladjev
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 AUTHORS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # Option combination generator
2
+
3
+ [![Travis test status](https://travis-ci.org/andrew-aladev/ocg.svg?branch=master)](https://travis-ci.org/andrew-aladev/ocg)
4
+ [![AppVeyor test status](https://ci.appveyor.com/api/projects/status/github/andrew-aladev/ocg?branch=master&svg=true)](https://ci.appveyor.com/project/andrew-aladev/ocg/branch/master)
5
+ [![Cirrus test status](https://api.cirrus-ci.com/github/andrew-aladev/ocg.svg?branch=master)](https://cirrus-ci.com/github/andrew-aladev/ocg)
6
+ [![Circle test status](https://circleci.com/gh/andrew-aladev/ocg/tree/master.svg?style=shield)](https://circleci.com/gh/andrew-aladev/ocg/tree/master)
7
+
8
+ ## Installation
9
+
10
+ ```sh
11
+ gem install ocg
12
+ ```
13
+
14
+ You can build it from source.
15
+
16
+ ```sh
17
+ rake gem
18
+ gem install pkg/ocg-*.gem
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require "ocg"
25
+
26
+ generator = OCG.new(
27
+ :a => %w[a b],
28
+ :b => 1..2
29
+ )
30
+ .or(
31
+ :c => %i[c d],
32
+ :d => 3..4
33
+ )
34
+ .and(
35
+ :e => %w[e f],
36
+ :f => 5..6
37
+ )
38
+ .mix(
39
+ :g => %i[g h],
40
+ :h => 7..8
41
+ )
42
+
43
+ until generator.finished?
44
+ pp generator.next
45
+ end
46
+ ```
47
+
48
+ It will populate all option combinations.
49
+
50
+ ## Docs
51
+
52
+ Options should be prepared in the following form:
53
+
54
+ ```ruby
55
+ {
56
+ option_name => option_values,
57
+ ...
58
+ }
59
+ ```
60
+
61
+ Options hash should not be empty.
62
+ `option_name` can be any valid hash key.
63
+ `option_values` should be convertable to array using `to_a`.
64
+ `option_values` should not be empty.
65
+
66
+ `OCG.new options` will prepare a generator.
67
+ It will provide all possible option combinations.
68
+
69
+ You can combine generators using `and`, `mix` and `or`.
70
+
71
+ `and` method will provide all combinations between generators.
72
+ `mix` method will merge right generator combinations into left without combining. `mix` guarantees that both left and right generator combinations will be provided at least once.
73
+ `or` method will concat generator combinations without merging.
74
+
75
+ `reset` method allows to receive combinations once again.
76
+
77
+ `next` method returns next combination.
78
+
79
+ `last` method returns last combination.
80
+
81
+ `started?` method returns true when at least one combination was generated.
82
+
83
+ `finished?` method returns true when all combination were generated.
84
+
85
+ `length` returns combinations length.
86
+
87
+ ## Why?
88
+
89
+ Many software uses multiple options and have complex relations between them.
90
+ We want to test this software.
91
+ We need to provide optimal option combination to maximize test coverage.
92
+
93
+ Let's look at [zstd compressor options](http://facebook.github.io/zstd/zstd_manual.html#Chapter5).
94
+
95
+ `compressionLevel` option is a preset for `windowLog`, `hashLog`, etc options.
96
+ We may set `compressionLevel` or other options, mixing is pointless.
97
+ We can say that there are 2 option groups: group with single `compressionLevel` option and group with other options.
98
+
99
+ ```ruby
100
+ general_generator = OCG.new(
101
+ :compressionLevel => -10..10
102
+ )
103
+ .or(
104
+ :windowLog => 0..10,
105
+ :hashLog => 0..10,
106
+ ...
107
+ )
108
+ ```
109
+
110
+ `enableLongDistanceMatching` option enables usage of `ldmHashLog`, `ldmMinMatch`, etc options.
111
+ We may use `:enableLongDistanceMatching => false` or `:enableLongDistanceMatching => true` with other options.
112
+
113
+ ```ruby
114
+ ldm_generator = OCG.new(
115
+ :enableLongDistanceMatching => [false]
116
+ )
117
+ .or(
118
+ :enableLongDistanceMatching => [true],
119
+ :ldmHashLog => 0..10,
120
+ :ldmMinMatch => 0..10,
121
+ ...
122
+ )
123
+ ```
124
+
125
+ General compression and long distance matching option groups correlate between each other.
126
+ We want to have all possible combinations between these option groups.
127
+
128
+ ```ruby
129
+ main_generator = general_generator.and ldm_generator
130
+ ```
131
+
132
+ `contentSizeFlag`, `checksumFlag`, `dictIDFlag` options are additional options.
133
+ These options don't correlate between each other or with main options.
134
+ We want just to mix their values with main options.
135
+
136
+ ```ruby
137
+ almost_complete_generator = main_generator.mix(
138
+ :contentSizeFlag => [true, false]
139
+ )
140
+ .mix(
141
+ :checksumFlag => [true, false]
142
+ )
143
+ .mix(
144
+ :dictIDFlag => [true, false]
145
+ )
146
+ ```
147
+
148
+ `nbWorkers`, `jobSize` and `overlapLog` options are thread related options.
149
+ These options correlate between each other but don't correlate with main options.
150
+
151
+ ```ruby
152
+ complete_generator = almost_complete_generator.mix(
153
+ :nbWorkers => 0..2,
154
+ :jobSize => 1..10,
155
+ :overlapLog => 0..9
156
+ )
157
+ ```
158
+
159
+ ## CI
160
+
161
+ Travis and Appveyor CI uses [scripts/toolchains.sh](scripts/toolchains.sh) directly.
162
+ Cirrus and Circle CI uses prebuilt [scripts/test-images](scripts/test-images).
163
+ Cirrus CI uses amd64 image, Circle CI - i686.
164
+
165
+ ## License
166
+
167
+ MIT license, see LICENSE and AUTHORS.
data/lib/ocg.rb ADDED
@@ -0,0 +1,5 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "ocg/main"
5
+ require_relative "ocg/version"
data/lib/ocg/error.rb ADDED
@@ -0,0 +1,9 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ class OCG
5
+ class BaseError < ::StandardError; end
6
+
7
+ class NotImplementedError < BaseError; end
8
+ class ValidateError < BaseError; end
9
+ end
data/lib/ocg/main.rb ADDED
@@ -0,0 +1,41 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require "forwardable"
5
+
6
+ require_relative "error"
7
+ require_relative "options"
8
+
9
+ class OCG
10
+ extend ::Forwardable
11
+
12
+ DELEGATORS = %i[reset next last started? finished? length].freeze
13
+
14
+ def initialize(generator_or_options)
15
+ @generator = self.class.prepare_generator generator_or_options
16
+ end
17
+
18
+ def self.prepare_generator(generator_or_options)
19
+ return generator_or_options if generator_or_options.is_a? OCG
20
+
21
+ Options.new generator_or_options
22
+ end
23
+
24
+ def_delegators :@generator, *DELEGATORS
25
+
26
+ def and(generator_or_options)
27
+ Operator::AND.new self, generator_or_options
28
+ end
29
+
30
+ def mix(generator_or_options)
31
+ Operator::MIX.new self, generator_or_options
32
+ end
33
+
34
+ def or(generator_or_options)
35
+ Operator::OR.new self, generator_or_options
36
+ end
37
+ end
38
+
39
+ require_relative "operator/and"
40
+ require_relative "operator/mix"
41
+ require_relative "operator/or"
@@ -0,0 +1,44 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "../error"
5
+
6
+ class OCG
7
+ module Operator
8
+ class Abstract < OCG
9
+ def initialize(left_generator_or_options, right_generator_or_options)
10
+ @left_generator = OCG.prepare_generator left_generator_or_options
11
+ @right_generator = OCG.prepare_generator right_generator_or_options
12
+
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @left_generator.reset
18
+ @right_generator.reset
19
+
20
+ nil
21
+ end
22
+
23
+ def next
24
+ raise NotImplementedError, "\"next\" is not implemented"
25
+ end
26
+
27
+ def last
28
+ raise NotImplementedError, "\"last\" is not implemented"
29
+ end
30
+
31
+ def started?
32
+ raise NotImplementedError, "\"started?\" is not implemented"
33
+ end
34
+
35
+ def finished?
36
+ raise NotImplementedError, "\"finished?\" is not implemented"
37
+ end
38
+
39
+ def length
40
+ raise NotImplementedError, "\"length\" is not implemented"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "abstract"
5
+
6
+ class OCG
7
+ module Operator
8
+ class AND < Abstract
9
+ def next
10
+ return nil if finished?
11
+
12
+ if @right_generator.finished?
13
+ @right_generator.reset
14
+ @left_generator.next.merge @right_generator.next
15
+ else
16
+ left_last = @left_generator.last
17
+ left_last = @left_generator.next if left_last.nil?
18
+ left_last.merge @right_generator.next
19
+ end
20
+ end
21
+
22
+ def last
23
+ left_last = @left_generator.last
24
+ right_last = @right_generator.last
25
+
26
+ return nil if left_last.nil? || right_last.nil?
27
+
28
+ left_last.merge right_last
29
+ end
30
+
31
+ def started?
32
+ @left_generator.started? || @right_generator.started?
33
+ end
34
+
35
+ def finished?
36
+ @left_generator.finished? && @right_generator.finished?
37
+ end
38
+
39
+ def length
40
+ @left_generator.length * @right_generator.length
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "abstract"
5
+
6
+ class OCG
7
+ module Operator
8
+ class MIX < Abstract
9
+ def initialize(*args)
10
+ super
11
+
12
+ @main_generator = \
13
+ if @right_generator.length > @left_generator.length
14
+ @right_generator
15
+ else
16
+ @left_generator
17
+ end
18
+ end
19
+
20
+ def next
21
+ return nil if finished?
22
+
23
+ @left_generator.reset if @left_generator.finished?
24
+ @right_generator.reset if @right_generator.finished?
25
+ @left_generator.next.merge @right_generator.next
26
+ end
27
+
28
+ def last
29
+ left_last = @left_generator.last
30
+ right_last = @right_generator.last
31
+
32
+ return nil if left_last.nil? || right_last.nil?
33
+
34
+ left_last.merge right_last
35
+ end
36
+
37
+ def started?
38
+ @main_generator.started?
39
+ end
40
+
41
+ def finished?
42
+ @main_generator.finished?
43
+ end
44
+
45
+ def length
46
+ @main_generator.length
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "abstract"
5
+
6
+ class OCG
7
+ module Operator
8
+ class OR < Abstract
9
+ def next
10
+ return nil if finished?
11
+
12
+ if @left_generator.finished?
13
+ @right_generator.next
14
+ else
15
+ @left_generator.next
16
+ end
17
+ end
18
+
19
+ def last
20
+ left_last = @left_generator.last
21
+ right_last = @right_generator.last
22
+
23
+ if right_last.nil?
24
+ left_last
25
+ else
26
+ right_last
27
+ end
28
+ end
29
+
30
+ def started?
31
+ @left_generator.started? || @right_generator.started?
32
+ end
33
+
34
+ def finished?
35
+ @left_generator.finished? && @right_generator.finished?
36
+ end
37
+
38
+ def length
39
+ @left_generator.length + @right_generator.length
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,77 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "validation"
5
+
6
+ class OCG
7
+ class Options
8
+ def initialize(options)
9
+ Validation.validate_options options
10
+ @options = Hash[options.map { |name, values| [name, values.to_a] }]
11
+
12
+ # End to start is more traditional way of making combinations.
13
+ @keys = @options.keys.reverse
14
+
15
+ @last_options = nil
16
+
17
+ reset_value_indexes
18
+
19
+ @is_finished = false
20
+ end
21
+
22
+ protected def reset_value_indexes
23
+ @value_indexes = Hash[@options.map { |name, _values| [name, 0] }]
24
+ end
25
+
26
+ def reset
27
+ return nil unless started?
28
+
29
+ @last_options = nil
30
+
31
+ # If state is finished than all value indexes are already zero.
32
+ reset_value_indexes unless @is_finished
33
+
34
+ @is_finished = false
35
+
36
+ nil
37
+ end
38
+
39
+ def next
40
+ return nil if @is_finished
41
+
42
+ @last_options = Hash[@value_indexes.map { |name, value_index| [name, @options[name][value_index]] }]
43
+
44
+ @is_finished = @keys.all? do |name|
45
+ values = @options[name]
46
+ new_value_index = @value_indexes[name] + 1
47
+
48
+ if new_value_index < values.length
49
+ @value_indexes[name] = new_value_index
50
+ next false
51
+ end
52
+
53
+ # Reset value index to zero.
54
+ @value_indexes[name] = 0
55
+ true
56
+ end
57
+
58
+ @last_options
59
+ end
60
+
61
+ def last
62
+ @last_options
63
+ end
64
+
65
+ def started?
66
+ !@last_options.nil?
67
+ end
68
+
69
+ def finished?
70
+ @is_finished
71
+ end
72
+
73
+ def length
74
+ @options.reduce(1) { |length, (_name, values)| length * values.length }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,18 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "error"
5
+
6
+ class OCG
7
+ module Validation
8
+ def self.validate_options(options)
9
+ raise ValidateError, "invalid options hash" unless options.is_a? ::Hash
10
+ raise ValidateError, "options should not be empty" if options.empty?
11
+
12
+ options.each do |_name, values|
13
+ raise ValidateError, "option values should respond to \"to_a\"" unless values.respond_to? :to_a
14
+ raise ValidateError, "option values should not be empty" if values.to_a.empty?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ # Option combination generator.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ class OCG
5
+ VERSION = "1.0.0".freeze
6
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ocg
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Aladjev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.75'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.75'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop-performance
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.3'
69
+ description:
70
+ email: aladjev.andrew@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - AUTHORS
76
+ - LICENSE
77
+ - README.md
78
+ - lib/ocg.rb
79
+ - lib/ocg/error.rb
80
+ - lib/ocg/main.rb
81
+ - lib/ocg/operator/abstract.rb
82
+ - lib/ocg/operator/and.rb
83
+ - lib/ocg/operator/mix.rb
84
+ - lib/ocg/operator/or.rb
85
+ - lib/ocg/options.rb
86
+ - lib/ocg/validation.rb
87
+ - lib/ocg/version.rb
88
+ homepage: https://github.com/andrew-aladev/ocg
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubygems_version: 3.0.6
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Option combination generator.
111
+ test_files: []