dither 0.1.5 → 0.2.0.rc3

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.
@@ -0,0 +1,38 @@
1
+ /*
2
+ *
3
+ * Copyright (C) 2015 Jason Gowan
4
+ * All rights reserved.
5
+ *
6
+ * This software may be modified and distributed under the terms
7
+ * of the BSD license. See the LICENSE file for details.
8
+ */
9
+
10
+ #ifndef SIMPLE_CONSTRAINT_HANDLER_H_
11
+ #define SIMPLE_CONSTRAINT_HANDLER_H_
12
+
13
+ #include <vector>
14
+ #include <utility>
15
+ #include <algorithm>
16
+ #include "dither_types.h"
17
+ #include "base_constraint_handler.h"
18
+
19
+ namespace dither {
20
+
21
+ class SimpleConstraintHandler : public BaseConstraintHandler {
22
+ protected:
23
+ std::vector<std::vector<std::pair<std::size_t, dval>>> constraints;
24
+ std::vector<dval> params;
25
+
26
+ inline bool violate_constraint(const dtest_case& test_case, const std::vector<std::pair<std::size_t, dval>>& constraint);
27
+ inline bool violate_constraint(const std::vector<param>& test_case, const std::vector<std::pair<std::size_t, dval>>& constraint);
28
+
29
+ public:
30
+ SimpleConstraintHandler(std::vector<dval>& ranges, std::vector<std::vector<dval>>& pconstraints);
31
+ bool violate_constraints(const dtest_case &test_case);
32
+ bool violate_constraints(const std::vector<param> &test_case);
33
+ bool ground(dtest_case &test_case);
34
+ };
35
+ }
36
+
37
+ #endif // SIMPLE_CONSTRAINT_HANDLER_H_
38
+
data/lib/dither/api.rb ADDED
@@ -0,0 +1,20 @@
1
+
2
+ require 'ffi'
3
+
4
+ # Interface to the c++ api.
5
+ module Dither
6
+ module API
7
+ extend FFI::Library
8
+ ffi_lib %w[lib/dither.so lib/dither.dll]
9
+
10
+ attach_function :dither_ipog_new, [:int], :pointer
11
+ attach_function :dither_ipog_add_parameter_int, [:pointer, :int, :pointer, :int], :void
12
+ attach_function :dither_ipog_run, [:pointer], :void
13
+ attach_function :dither_ipog_size, [:pointer], :int
14
+ attach_function :dither_ipog_display_raw_solution, [:pointer], :void
15
+ attach_function :dither_ipog_fill, [:pointer, :pointer], :void
16
+ attach_function :dither_ipog_add_constraint, [:pointer, :pointer, :int], :void
17
+ attach_function :dither_ipog_add_previously_tested, [:pointer, :pointer, :int], :void
18
+ # attach_function :dither_ipog_delete, [:pointer], :void
19
+ end
20
+ end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Dither
3
- VERSION = '0.1.5'
3
+ VERSION = '0.2.0.rc3'
4
4
  end
data/lib/dither.rb CHANGED
@@ -12,18 +12,73 @@ module Dither
12
12
  # deprecated
13
13
  def self.all_pairs(params, t = 2, opts = {})
14
14
  opts[:t] = t
15
- IPOG.new(params, opts).run
15
+ ipog(params, opts)
16
16
  end
17
17
 
18
18
  def self.ipog(params, opts = {})
19
19
  opts = DEFUALT_OPTS.dup.merge(opts)
20
- IPOG.new(params, opts).run
21
- end
20
+ t = opts[:t] || 2
21
+ if t < 2
22
+ raise Dither::Error,'t must be >= 2'
23
+ end
24
+ raise Dither::Error, 'param length must be > 1' if params.any? { |a| a.size <= 1 }
25
+ if t > params.size
26
+ raise Dither::Error, 't must be <= params.length'
27
+ end
22
28
 
23
- def self.mipog(params, t = 2, opts = {})
24
- raise Error, 'mipog does not support constraints' if opts.key?(:constraints)
25
- opts[:t] = t
26
- MIPOG.new(params, opts).run
29
+ pointer = Dither::API.dither_ipog_new(t)
30
+ c_params = (0..params.max { |a| a.size }.size).to_a
31
+ c_int_params = FFI::MemoryPointer.new(:int, c_params.size)
32
+ c_int_params.write_array_of_int(c_params)
33
+
34
+ params.each_with_index do |param, i|
35
+ Dither::API.dither_ipog_add_parameter_int(pointer, i, c_int_params, param.size)
36
+ end
37
+
38
+ if opts[:constraints]
39
+ constraint_scratch = FFI::MemoryPointer.new(:int, params.size)
40
+ opts[:constraints].each do |constraint|
41
+ arr = Array.new(params.size, -1)
42
+ constraint.each do |k, v|
43
+ if k >= params.size
44
+ raise Dither::Error, "Invalid constraint #{k} > #{params.size}"
45
+ end
46
+ if v >= params[k].size
47
+ raise Dither::Error, "Invalid constraint #{k} > #{params[k].size}"
48
+
49
+ end
50
+ arr[k] = v
51
+ end
52
+ constraint_scratch.write_array_of_int(arr)
53
+ Dither::API.dither_ipog_add_constraint(pointer, constraint_scratch, params.size)
54
+ end
55
+ end
56
+
57
+ if opts[:previously_tested]
58
+ tested_scratch = FFI::MemoryPointer.new(:int, params.size)
59
+ opts[:previously_tested].each do |test_case|
60
+ if test_case.size != params.size
61
+ raise Dither::Error
62
+ end
63
+ arr = Array.new(params.size)
64
+ (0...params.size).each do |i|
65
+ arr[i] = params[i].find(test_case[i]).first
66
+ end
67
+ tested_scratch.write_array_of_int(arr)
68
+ Dither::API.dither_ipog_add_previously_tested(pointer, tested_scratch, params.size)
69
+ end
70
+ end
71
+
72
+ Dither::API.dither_ipog_run(pointer)
73
+ result_size = Dither::API.dither_ipog_size(pointer)
74
+ solution = FFI::MemoryPointer.new(:int, params.size * result_size)
75
+ Dither::API.dither_ipog_fill(pointer, solution)
76
+
77
+ results = solution.read_array_of_int(params.size * result_size)
78
+ .enum_for(:each_slice, params.size)
79
+ .map do |test_case|
80
+ test_case.zip(params).map { |a, b| b[a] }
81
+ end
27
82
  end
28
83
 
29
84
  def self.aetg(params, opts = {})
@@ -34,12 +89,6 @@ module Dither
34
89
  class << self; alias_method :ateg, :aetg end
35
90
  end # Dither
36
91
 
37
- require 'dither/param'
38
- require 'dither/unbound_param'
39
- require 'dither/test_case'
40
- require 'dither/ipog_helper'
41
- require 'dither/ipog'
42
- require 'dither/mipog'
43
92
  require 'dither/chinese_postman_problem'
44
93
  require 'dither/aetg'
45
94
  require 'dither/aetg_pairwise'
@@ -51,4 +100,6 @@ if RUBY_PLATFORM =~ /java/
51
100
  require 'dither-0.1.3.jar'
52
101
 
53
102
  require 'dither/java_ext/dither'
103
+ else
104
+ require 'dither/api'
54
105
  end
@@ -2,10 +2,6 @@ require File.expand_path('../../spec_helper.rb', __FILE__)
2
2
 
3
3
  describe Dither do
4
4
 
5
- it 'mipog does not support constraints' do
6
- expect { Dither.mipog([[1,1],[1,2]], 2, :constraints => []) }.to raise_error(Dither::Error, 'mipog does not support constraints')
7
- end
8
-
9
5
  it 't must be >= 2' do
10
6
  expect { Dither.ipog([], :t => 0) }.to raise_error(Dither::Error, 't must be >= 2')
11
7
  end
@@ -20,63 +16,15 @@ describe Dither do
20
16
 
21
17
  it 'can compute 2-way ipog using symbols' do
22
18
  params = [[:a, :b, :c], [:d, :e, :f], [:h, :i]]
23
- expect(Dither.ipog(params)).to eq([[:a, :d, :h],
24
- [:a, :e, :i],
25
- [:a, :f, :h],
26
- [:b, :d, :i],
27
- [:b, :e, :h],
28
- [:b, :f, :i],
29
- [:c, :d, :h],
30
- [:c, :e, :i],
31
- [:c, :f, :h]])
32
- end
33
-
34
- it 'can compute 3-way mipog' do
35
- params = [(0...2).to_a, (0...2).to_a, (0..3).to_a]
36
- expect(Dither.mipog(params, 3)).to eq([[0, 0, 0],
37
- [1, 0, 0],
38
- [0, 1, 0],
39
- [1, 1, 0],
40
- [0, 0, 1],
41
- [1, 0, 1],
42
- [0, 1, 1],
43
- [1, 1, 1],
44
- [0, 0, 2],
45
- [1, 0, 2],
46
- [0, 1, 2],
47
- [1, 1, 2],
48
- [0, 0, 3],
49
- [1, 0, 3],
50
- [0, 1, 3],
51
- [1, 1, 3],
52
- ])
53
- end
54
-
55
- it 'can compute 2-way mipog using symbols' do
56
- params = [[:a, :b, :c], [:d, :e, :f], [:h, :i]]
57
- expect(Dither.mipog(params).to_set).to eq([[:a, :d, :h],
58
- [:a, :e, :i],
59
- [:a, :f, :h],
60
- [:b, :d, :i],
61
- [:b, :e, :h],
62
- [:b, :f, :i],
63
- [:c, :d, :h],
64
- [:c, :e, :i],
65
- [:c, :f, :h]].to_set)
66
- end
67
-
68
- it 'can compute 2-way mipog' do
69
- params = [(0...2).to_a, (0..3).to_a]
70
- expect(Dither.mipog(params)).to eq([
71
- [0, 0],
72
- [1, 0],
73
- [0, 1],
74
- [1, 1],
75
- [0, 2],
76
- [1, 2],
77
- [0, 3],
78
- [1, 3],
79
- ])
19
+ expect(Dither.all_pairs(params)).to eq([[:c, :f, :h],
20
+ [:b, :f, :i],
21
+ [:a, :f, :h],
22
+ [:c, :e, :i],
23
+ [:b, :e, :h],
24
+ [:a, :e, :i],
25
+ [:c, :d, :h],
26
+ [:b, :d, :i],
27
+ [:a, :d, :h]])
80
28
  end
81
29
 
82
30
  it 'can compute 2-way ipog' do
@@ -90,7 +38,7 @@ describe Dither do
90
38
  [1, 2],
91
39
  [0, 3],
92
40
  [1, 3],
93
- ])
41
+ ].reverse)
94
42
  end
95
43
 
96
44
  it 'can compute 3-way ipog' do
@@ -116,54 +64,37 @@ describe Dither do
116
64
 
117
65
  it 'can compute 3-way ipog with constraints' do
118
66
  params = [(0...2).to_a, (0...2).to_a, (0..3).to_a]
119
- expect(Dither.ipog(params, :t => 3,
67
+ results = Dither.ipog(params, :t => 3,
120
68
  :constraints => [
121
69
  {0 => 0,
122
70
  2 => 2},
123
71
  {0 => 0,
124
72
  1 => 1,
125
73
  2 => 0}],
126
- :previously_tested => [[0, 0, 0]]).to_set).to eq([
127
- [1, 0, 0],
128
- [1, 1, 0],
129
- [0, 0, 1],
130
- [1, 0, 1],
131
- [0, 1, 1],
132
- [1, 1, 1],
133
- [1, 0, 2],
134
- [1, 1, 2],
135
- [0, 0, 3],
136
- [1, 0, 3],
137
- [0, 1, 3],
138
- [1, 1, 3],
139
- ].to_set)
74
+ :previously_tested => [[0, 0, 0]]
75
+ )
76
+ results.each do |result|
77
+ expect(result[0] == 0 && result[1] == 1 && result[2] == 0).to be false
78
+ end
79
+ results.each do |result|
80
+ expect(result[0] == 0 && result[1] == 2).to be false
81
+ end
82
+ results.each do |result|
83
+ expect(result[0] == 0 && result[1] == 0 && result[2] == 0).to be false
84
+ end
140
85
  end
141
86
 
142
87
  it 'another 3-way ipog with constraints' do
143
88
  params = [(0...2).to_a, (0...2).to_a, (0...2).to_a, (0..3).to_a]
144
- expect(Dither.ipog(params, :t => 3,
89
+ results = Dither.ipog(params, :t => 3,
145
90
  :constraints => [
146
91
  {0 => 0,
147
92
  1 => 1,
148
93
  2 => 0}
149
- ]).to_set).to eq([[0, 0, 0, 0],
150
- [1, 1, 0, 0],
151
- [1, 0, 1, 0],
152
- [0, 1, 1, 0],
153
- [1, 0, 0, 1],
154
- [1, 1, 0, 1],
155
- [0, 0, 1, 1],
156
- [1, 1, 1, 1],
157
- [0, 0, 0, 2],
158
- [1, 1, 0, 2],
159
- [1, 0, 1, 2],
160
- [0, 1, 1, 2],
161
- [0, 0, 0, 3],
162
- [1, 1, 0, 3],
163
- [1, 0, 1, 3],
164
- [0, 1, 1, 3],
165
- [0, 0, 0, 1],
166
- [0, 1, 1, 1]].to_set)
94
+ ])
95
+ results.each do |result|
96
+ expect(result[0] == 0 && result[1] == 1 && result[2] == 0).to be false
97
+ end
167
98
  end
168
99
 
169
100
  it 'can run 2-way aetg' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dither
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Gowan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-21 00:00:00.000000000 Z
11
+ date: 2015-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.9.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: coveralls
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,11 +66,26 @@ dependencies:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ffi
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
55
83
  description: Efficient test generation strategies
56
84
  email:
57
85
  - gowanjason@gmail.com
58
86
  executables: []
59
- extensions: []
87
+ extensions:
88
+ - ext/dither/extconf.rb
60
89
  extra_rdoc_files: []
61
90
  files:
62
91
  - ".gitignore"
@@ -68,18 +97,24 @@ files:
68
97
  - README.md
69
98
  - Rakefile
70
99
  - dither.gemspec
100
+ - ext/dither/README.md
101
+ - ext/dither/base_constraint_handler.h
102
+ - ext/dither/combinations.h
103
+ - ext/dither/dither.cc
104
+ - ext/dither/dither.h
105
+ - ext/dither/dither_types.h
106
+ - ext/dither/extconf.rb
107
+ - ext/dither/ipog.cc
108
+ - ext/dither/ipog.h
109
+ - ext/dither/simple_constraint_handler.cc
110
+ - ext/dither/simple_constraint_handler.h
71
111
  - lib/dither.rb
72
112
  - lib/dither/aetg.rb
73
113
  - lib/dither/aetg_pairwise.rb
114
+ - lib/dither/api.rb
74
115
  - lib/dither/chinese_postman_problem.rb
75
116
  - lib/dither/graph.rb
76
- - lib/dither/ipog.rb
77
- - lib/dither/ipog_helper.rb
78
117
  - lib/dither/java_ext/dither.rb
79
- - lib/dither/mipog.rb
80
- - lib/dither/param.rb
81
- - lib/dither/test_case.rb
82
- - lib/dither/unbound_param.rb
83
118
  - lib/dither/version.rb
84
119
  - spec/dither/chinese_postman_problem_spec.rb
85
120
  - spec/dither/dither_spec.rb
@@ -100,12 +135,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
135
  version: '0'
101
136
  required_rubygems_version: !ruby/object:Gem::Requirement
102
137
  requirements:
103
- - - ">="
138
+ - - ">"
104
139
  - !ruby/object:Gem::Version
105
- version: '0'
140
+ version: 1.3.1
106
141
  requirements: []
107
142
  rubyforge_project: dither
108
- rubygems_version: 2.2.0
143
+ rubygems_version: 2.4.5.1
109
144
  signing_key:
110
145
  specification_version: 4
111
146
  summary: Collection of test generation strategies
data/lib/dither/ipog.rb DELETED
@@ -1,58 +0,0 @@
1
- # coding: utf-8
2
-
3
- module Dither
4
- class IPOG
5
- include IPOGHelper
6
-
7
- def run
8
- # add into test set a test for each combination of values
9
- # of the first t parameter
10
- test_set = comb
11
-
12
- (t...params.length).each do |i|
13
- # let pi
14
- # be the set of t-way combinations of values involving
15
- # parameter Pi and t -1 parameters among the first i – 1
16
- # parameters
17
- pi = comb_i(i)
18
-
19
- # horizontal extension for parameter i
20
- test_set.each do |test_case|
21
- cover = maximize_coverage(i, test_case, pi)
22
-
23
- if cover.nil?
24
- test_set.delete(test_case)
25
- else
26
- pi -= cover
27
- end
28
- end
29
-
30
- # vertical extension for parameter i
31
- pi.each do |a|
32
- if test_set.any? { |b| a.subset?(b) }
33
- pi.delete(a)
34
- else
35
-
36
- test_case = nil
37
- test_set.each do |b|
38
- test_case = b.merge_without_conflict(i, a) do |a|
39
- violates_constraints?(a)
40
- end
41
- break unless test_case.nil?
42
- end
43
-
44
- if test_case.nil?
45
- test_set << a.create_unbound(i)
46
- end
47
- pi.delete(a)
48
- end
49
- end
50
- end
51
-
52
- @test_set = test_set.map { |a| fill_unbound(a) }
53
- .delete_if(&:nil?)
54
- .to_a
55
- @test_set
56
- end
57
- end # IPOG
58
- end # Dither
@@ -1,161 +0,0 @@
1
- # coding: utf-8
2
-
3
- module Dither
4
- module IPOGHelper
5
- attr_reader :params, :t, :constraints, :test_set, :orig_params, :unbound_param_pool, :tested
6
- private :params, :t, :constraints, :test_set, :orig_params, :unbound_param_pool, :tested
7
-
8
- def initialize(params, opts = {})
9
- init_params(params, (opts[:previously_tested] || []))
10
- @t = opts[:t]
11
- unless opts[:constraints].nil?
12
- @constraints = opts[:constraints].map(&:to_a)
13
- .map { |a| a.map { |b| @params[@map_to_orig_index.key(b[0])][b[1]] } }
14
- .map(&:to_set)
15
- end
16
-
17
- raise Dither::Error, 't must be >= 2' if opts[:t] < 2
18
- raise Dither::Error, 't must be <= params.length' if opts[:t] > params.length
19
- params.each do |param|
20
- raise Dither::Error, 'param length must be > 1' if param.length < 2
21
- end
22
- end
23
-
24
- def init_params(user_params, previously_tested)
25
- tmp = []
26
- @input_params = user_params
27
- user_params.each_with_index { |e, i| tmp << [i, e] }
28
- @orig_params = tmp.sort_by { |a| a[1].length }
29
- .reverse!
30
-
31
- orig_param_map = {}
32
- @map_to_orig_index = {}
33
- @orig_params.each_with_index do |e, i|
34
- @map_to_orig_index[i] = e[0]
35
- orig_param_map[e[0]] = {}
36
- end
37
-
38
- @params = []
39
- @unbound_param_pool = []
40
- orig_params.each_with_index do |e, i|
41
- @params << (0...e[1].length).map do |j|
42
- local_param = Param.new(i, j)
43
- orig_param_map[e[0]][e[1][j]] = local_param
44
- local_param
45
- end
46
- @unbound_param_pool << UnboundParam.new(i)
47
- end
48
-
49
- @tested = []
50
- previously_tested.each do |a|
51
- local_params = []
52
- a.each_with_index do |e, i|
53
- local_params << orig_param_map[i][e]
54
- end
55
- @tested << TestCase.create(params, unbound_param_pool, local_params)
56
- end
57
-
58
- params
59
- end
60
-
61
- # return nil if unable to satisfy constraints
62
- def maximize_coverage(i, test_case, pi)
63
- current_max = 0
64
- current_max_j = 0
65
- current_matches = []
66
-
67
- (0...params[i].length).each do |j|
68
- current_param = params[i][j]
69
- test_case << current_param
70
- unless violates_constraints?(test_case)
71
- matches = pi.select { |a| a.subset?(test_case) }
72
- count = matches.count
73
-
74
- if count > current_max
75
- current_max = count
76
- current_max_j = j
77
- current_matches = matches
78
- end
79
- end
80
- test_case.delete(current_param)
81
- end
82
-
83
- return nil if violates_constraints?(test_case)
84
- test_case << params[i][current_max_j]
85
-
86
- current_matches
87
- end
88
-
89
- def violates_constraints?(params)
90
- return false if constraints.nil?
91
- constraints.any? { |b| b.subset?(params) }
92
- end
93
-
94
- private
95
-
96
- def comb
97
- ranges = (0...t).to_a.inject([]) do |a, i|
98
- a << (0...params[i].length).map { |j| params[i][j] }
99
- end
100
-
101
- products = ranges[1..-1].inject(ranges[0]) do |a, b|
102
- a = a.product(b)
103
- end
104
-
105
- result = products.map(&:flatten)
106
- .map { |a| TestCase.create(params, unbound_param_pool, a) }
107
- result.reject { |a| tested?(a) }
108
- end
109
-
110
- def comb_i(param_i)
111
- values = (0...param_i).to_a.combination((t-1)).to_a
112
- values.each do |a|
113
- a << param_i
114
- end
115
- result = []
116
- values.each do |a|
117
- result += a[1..-1]
118
- .inject((0...params[a[0]].length).map { |b| params[a[0]][b] }) { |p, i| p.product((0...params[i].length).to_a.map { |c| params[i][c] }) }
119
- .map(&:flatten)
120
- .map { |b| TestCase.create(params, unbound_param_pool, b) }
121
- end
122
- result.reject { |a| tested?(a) }.to_set
123
- end
124
-
125
- def tested?(test_case)
126
- @tested.any? { |a| test_case.subset?(a) }
127
- end
128
-
129
- def fill_unbound(data)
130
- arr = Array.new(params.length)
131
- data.each do |param|
132
- unless param.unbound?
133
- i = @map_to_orig_index[param.i]
134
- arr[i] = @input_params[i][param.j]
135
- end
136
- end
137
-
138
- arr.each_with_index do |e, i|
139
- next unless e.nil?
140
-
141
- orig_param = @input_params[i]
142
- (0...orig_param.length).each do |j|
143
- data << params[@map_to_orig_index.key(i)][j]
144
- if violates_constraints?(data)
145
- data.delete(params[@map_to_orig_index.key(i)][j])
146
- next
147
- else
148
- arr[i] = orig_param[j]
149
- break
150
- end
151
- end
152
- return nil if arr[i].nil?
153
- end
154
-
155
- return nil if violates_constraints?(data)
156
- return nil if tested?(data)
157
-
158
- arr
159
- end
160
- end # IPOGHelper
161
- end # Dither