ocg 1.0.0

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 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: []