delta_test 0.1.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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +4 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +165 -0
  8. data/Rakefile +17 -0
  9. data/bin/delta_test +12 -0
  10. data/circle.yml +12 -0
  11. data/delta_test.gemspec +30 -0
  12. data/lib/delta_test/analyzer.rb +47 -0
  13. data/lib/delta_test/cli.rb +224 -0
  14. data/lib/delta_test/configuration.rb +173 -0
  15. data/lib/delta_test/dependencies_table.rb +83 -0
  16. data/lib/delta_test/errors.rb +55 -0
  17. data/lib/delta_test/generator.rb +101 -0
  18. data/lib/delta_test/git.rb +88 -0
  19. data/lib/delta_test/related_spec_list.rb +64 -0
  20. data/lib/delta_test/spec_helpers.rb +42 -0
  21. data/lib/delta_test/utils.rb +93 -0
  22. data/lib/delta_test/version.rb +9 -0
  23. data/lib/delta_test.rb +47 -0
  24. data/spec/fixtures/sample/alpha.rb +19 -0
  25. data/spec/fixtures/sample/beta.rb +15 -0
  26. data/spec/fixtures/sample/gamma.rb +9 -0
  27. data/spec/lib/delta_test/analyzer_spec.rb +126 -0
  28. data/spec/lib/delta_test/cli_spec.rb +422 -0
  29. data/spec/lib/delta_test/configuration_spec.rb +353 -0
  30. data/spec/lib/delta_test/dependencies_table_spec.rb +129 -0
  31. data/spec/lib/delta_test/generator_spec.rb +201 -0
  32. data/spec/lib/delta_test/git_spec.rb +178 -0
  33. data/spec/lib/delta_test/related_spec_list_spec.rb +182 -0
  34. data/spec/lib/delta_test/spec_helpers_spec.rb +72 -0
  35. data/spec/lib/delta_test/utils_spec.rb +244 -0
  36. data/spec/lib/delta_test_spec.rb +119 -0
  37. data/spec/rails/.gitignore +19 -0
  38. data/spec/rails/.rspec +3 -0
  39. data/spec/rails/Gemfile +15 -0
  40. data/spec/rails/Gemfile.lock +163 -0
  41. data/spec/rails/README.rdoc +28 -0
  42. data/spec/rails/Rakefile +6 -0
  43. data/spec/rails/app/controllers/application_controller.rb +5 -0
  44. data/spec/rails/app/controllers/concerns/.keep +0 -0
  45. data/spec/rails/app/helpers/application_helper.rb +2 -0
  46. data/spec/rails/app/mailers/.keep +0 -0
  47. data/spec/rails/app/models/.keep +0 -0
  48. data/spec/rails/app/models/concerns/.keep +0 -0
  49. data/spec/rails/app/views/layouts/application.html.haml +7 -0
  50. data/spec/rails/bin/bundle +3 -0
  51. data/spec/rails/bin/rails +4 -0
  52. data/spec/rails/bin/rake +4 -0
  53. data/spec/rails/bin/setup +29 -0
  54. data/spec/rails/config/application.rb +35 -0
  55. data/spec/rails/config/boot.rb +3 -0
  56. data/spec/rails/config/database.yml +25 -0
  57. data/spec/rails/config/environment.rb +5 -0
  58. data/spec/rails/config/environments/development.rb +41 -0
  59. data/spec/rails/config/environments/production.rb +79 -0
  60. data/spec/rails/config/environments/test.rb +42 -0
  61. data/spec/rails/config/initializers/assets.rb +11 -0
  62. data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
  63. data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
  64. data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
  65. data/spec/rails/config/initializers/inflections.rb +16 -0
  66. data/spec/rails/config/initializers/mime_types.rb +4 -0
  67. data/spec/rails/config/initializers/session_store.rb +3 -0
  68. data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
  69. data/spec/rails/config/locales/en.yml +23 -0
  70. data/spec/rails/config/routes.rb +56 -0
  71. data/spec/rails/config/secrets.yml +22 -0
  72. data/spec/rails/config.ru +4 -0
  73. data/spec/rails/db/seeds.rb +7 -0
  74. data/spec/rails/delta_test.yml +5 -0
  75. data/spec/rails/lib/assets/.keep +0 -0
  76. data/spec/rails/lib/tasks/.keep +0 -0
  77. data/spec/rails/log/.keep +0 -0
  78. data/spec/rails/public/404.html +67 -0
  79. data/spec/rails/public/422.html +67 -0
  80. data/spec/rails/public/500.html +66 -0
  81. data/spec/rails/public/favicon.ico +0 -0
  82. data/spec/rails/public/robots.txt +5 -0
  83. data/spec/rails/spec/features/sample_spec.rb +7 -0
  84. data/spec/rails/spec/spec_helper.rb +16 -0
  85. data/spec/rails/vendor/assets/javascripts/.keep +0 -0
  86. data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
  87. data/spec/spec_helper.rb +39 -0
  88. data/spec/supports/create_table_file.rb +21 -0
  89. metadata +283 -0
@@ -0,0 +1,173 @@
1
+ require 'set'
2
+ require 'pathname'
3
+ require 'yaml'
4
+
5
+ require_relative 'git'
6
+ require_relative 'utils'
7
+
8
+ module DeltaTest
9
+ class Configuration
10
+
11
+ CONFIG_FILES = [
12
+ 'delta_test.yml',
13
+ 'delta_test.yaml',
14
+ ].freeze
15
+
16
+ attr_accessor *%i[
17
+ base_path
18
+ files
19
+
20
+ table_file
21
+ patterns
22
+ exclude_patterns
23
+ custom_mappings
24
+ ]
25
+
26
+ # for precalculated values
27
+ attr_reader *%i[
28
+ filtered_files
29
+ table_file_path
30
+ ]
31
+
32
+ def initialize
33
+ update do |c|
34
+ c.base_path = File.expand_path('.')
35
+ c.table_file = 'tmp/.delta_test_dt'
36
+ c.files = []
37
+ c.patterns = []
38
+ c.exclude_patterns = []
39
+ c.custom_mappings = {}
40
+ end
41
+ end
42
+
43
+
44
+ # Override setters
45
+ #-----------------------------------------------
46
+ ###
47
+ # Store base_path as Pathname
48
+ #
49
+ # @params {String|Pathname} path
50
+ # @return {Pathname}
51
+ ###
52
+ def base_path=(path)
53
+ @base_path = Pathname.new(path)
54
+ end
55
+
56
+ ###
57
+ # Store table_file as Pathname
58
+ #
59
+ # @params {String|Pathname} path
60
+ # @return {Pathname}
61
+ ###
62
+ def table_file=(path)
63
+ @table_file = Pathname.new(path)
64
+ end
65
+
66
+
67
+ # Update
68
+ #-----------------------------------------------
69
+ ###
70
+ # Update, verify and precalculate
71
+ #
72
+ # @block
73
+ ###
74
+ def update
75
+ yield self if block_given?
76
+ validate!
77
+ precalculate!
78
+ end
79
+
80
+ ###
81
+ # Validate option values
82
+ ###
83
+ def validate!
84
+ if self.base_path.relative?
85
+ raise ValidationError.new(:base_path, 'need to be an absolute path')
86
+ end
87
+
88
+ unless self.files.is_a?(Array)
89
+ raise ValidationError.new(:files, 'need to be an array')
90
+ end
91
+
92
+ unless self.patterns.is_a?(Array)
93
+ raise ValidationError.new(:patterns, 'need to be an array')
94
+ end
95
+
96
+ unless self.exclude_patterns.is_a?(Array)
97
+ raise ValidationError.new(:exclude_patterns, 'need to be an array')
98
+ end
99
+
100
+ unless self.custom_mappings.is_a?(Hash)
101
+ raise ValidationError.new(:custom_mappings, 'need to be a hash')
102
+
103
+ unless self.custom_mappings.values.all? { |v| v.is_a?(Array) }
104
+ raise ValidationError.new(:custom_mappings, 'need to have an array in the contents')
105
+ end
106
+ end
107
+ end
108
+
109
+ ###
110
+ # Precalculate some values
111
+ ###
112
+ def precalculate!
113
+ filtered_files = self.files
114
+ .map { |f| Utils.regulate_filepath(f, self.base_path) }
115
+ .uniq
116
+
117
+ filtered_files = Utils.files_grep(filtered_files, self.patterns, self.exclude_patterns)
118
+
119
+ @filtered_files = Set.new(filtered_files)
120
+
121
+ @table_file_path = Pathname.new(File.absolute_path(self.table_file, self.base_path))
122
+ end
123
+
124
+
125
+ # Auto configuration
126
+ #-----------------------------------------------
127
+ ###
128
+ # Use configuration file and git
129
+ ###
130
+ def auto_configure!
131
+ load_from_file!
132
+ retrive_files_from_git_index!
133
+ update
134
+ end
135
+
136
+ ###
137
+ # Load configuration file
138
+ # And update `base_path` to the directory
139
+ ###
140
+ def load_from_file!
141
+ config_file = Utils.find_file_upward(*CONFIG_FILES)
142
+
143
+ unless config_file
144
+ raise NoConfigurationFileFoundError
145
+ end
146
+
147
+ yaml = YAML.load_file(config_file)
148
+
149
+ self.base_path = File.dirname(config_file)
150
+
151
+ yaml.each do |k, v|
152
+ if self.respond_to?("#{k}=")
153
+ self.send("#{k}=", v)
154
+ else
155
+ raise InvalidOptionError.new(k)
156
+ end
157
+ end
158
+ end
159
+
160
+ ###
161
+ # Retrive files from git index
162
+ # And update `files`
163
+ ###
164
+ def retrive_files_from_git_index!
165
+ unless Git.git_repo?
166
+ raise NotInGitRepositoryError
167
+ end
168
+
169
+ self.files = Git.ls_files
170
+ end
171
+
172
+ end
173
+ end
@@ -0,0 +1,83 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+
4
+ require_relative 'utils'
5
+
6
+ module DeltaTest
7
+ class DependenciesTable < ::Hash
8
+
9
+ DEFAULT_PROC = -> (h, k) { h[k] = ::Set.new }
10
+
11
+ def initialize
12
+ super
13
+
14
+ self.default_proc = DEFAULT_PROC
15
+ end
16
+
17
+ ###
18
+ # Restore a table object from a file
19
+ #
20
+ # @params {String|Pathname} file
21
+ ###
22
+ def self.load(file)
23
+ begin
24
+ data = File.binread(file)
25
+ dt = Marshal.load(data)
26
+ dt.default_proc = DEFAULT_PROC
27
+ dt
28
+ rescue
29
+ self.new
30
+ end
31
+ end
32
+
33
+ ###
34
+ # Add a dependency for a spec file
35
+ #
36
+ # @params {String} spec_file
37
+ # @params {String} source_file
38
+ ###
39
+ def add(spec_file, source_file)
40
+ source_file = Utils.regulate_filepath(source_file, DeltaTest.config.base_path)
41
+ self[spec_file] << source_file if DeltaTest.config.filtered_files.include?(source_file)
42
+ end
43
+
44
+ ###
45
+ # Temporary disable default_proc
46
+ # Because Marshal can't dump Hash with default_proc
47
+ #
48
+ # @block
49
+ ###
50
+ def without_default_proc
51
+ self.default_proc = nil
52
+
53
+ begin
54
+ yield
55
+ ensure
56
+ self.default_proc = DEFAULT_PROC
57
+ end
58
+ end
59
+
60
+ ###
61
+ # Cleanup empty sets from the table
62
+ ###
63
+ def cleanup!
64
+ self.reject! { |k, v| v.empty? }
65
+ end
66
+
67
+ ###
68
+ # Dump the table object to a file
69
+ #
70
+ # @params {String|Pathname} file
71
+ ###
72
+ def dump(file)
73
+ # Marshal can't dump hash with default proc
74
+ without_default_proc do
75
+ cleanup!
76
+ data = Marshal.dump(self)
77
+ FileUtils.mkdir_p(File.dirname(file))
78
+ File.open(file, 'wb') { |f| f.write data }
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,55 @@
1
+ module DeltaTest
2
+
3
+ class TableNotFoundError < IOError
4
+
5
+ def initialize(table_file_path)
6
+ @table_file_path = table_file_path
7
+ end
8
+
9
+ def message
10
+ 'table file not found at: `%s`' % @table_file_path
11
+ end
12
+
13
+ end
14
+
15
+ class NotInGitRepositoryError < StandardError
16
+
17
+ def message
18
+ 'the directory is not managed by git'
19
+ end
20
+
21
+ end
22
+
23
+ class NoConfigurationFileFoundError < IOError
24
+
25
+ def message
26
+ 'no configuration file found'
27
+ end
28
+
29
+ end
30
+
31
+ class InvalidOptionError < StandardError
32
+
33
+ def initialize(option)
34
+ @option = option
35
+ end
36
+
37
+ def message
38
+ 'invalid option: %s' % @option
39
+ end
40
+
41
+ end
42
+
43
+ class ValidationError < StandardError
44
+
45
+ def initialize(name, message)
46
+ @name, @_message = name, message
47
+ end
48
+
49
+ def message
50
+ '`%s` %s' % [@name, @_message]
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'analyzer'
2
+ require_relative 'dependencies_table'
3
+
4
+ require_relative 'utils'
5
+
6
+ module DeltaTest
7
+ class Generator
8
+
9
+ attr_reader *%i[
10
+ current_spec_file
11
+ table
12
+ ]
13
+
14
+ ###
15
+ # Setup analyzer and table
16
+ #
17
+ # @params {Boolean} _auto_teardown
18
+ ###
19
+ def setup!(_auto_teardown = true)
20
+ return unless DeltaTest.active?
21
+
22
+ return if @_setup
23
+ @_setup = true
24
+
25
+ DeltaTest.log('--- setup!')
26
+
27
+ @analyzer = Analyzer.new
28
+ @table = DependenciesTable.load(DeltaTest.config.table_file_path)
29
+
30
+ @current_spec_file = nil
31
+
32
+ hook_on_exit { teardown! } if _auto_teardown
33
+ end
34
+
35
+ ###
36
+ # Start analyzer for the spec file
37
+ #
38
+ # @params {String} spec_file
39
+ ###
40
+ def start!(spec_file)
41
+ return unless DeltaTest.active?
42
+
43
+ DeltaTest.log('--- start!(%s)' % spec_file)
44
+
45
+ @current_spec_file = Utils.regulate_filepath(spec_file, DeltaTest.config.base_path).to_s
46
+ @analyzer.start
47
+ end
48
+
49
+ ###
50
+ # Stop analyzer and update table
51
+ ###
52
+ def stop!
53
+ return unless DeltaTest.active?
54
+
55
+ DeltaTest.log('--- stop!')
56
+
57
+ spec_file = @current_spec_file
58
+ @current_spec_file = nil
59
+
60
+ @analyzer.stop
61
+
62
+ if spec_file
63
+ @analyzer.related_source_files.each do |file|
64
+ @table.add(spec_file, file)
65
+ end
66
+ end
67
+ end
68
+
69
+ ###
70
+ # Save table to the file
71
+ ###
72
+ def teardown!
73
+ return unless @_setup
74
+ return if @_teardown
75
+ @_teardown = true
76
+
77
+ DeltaTest.log('--- teardown!')
78
+
79
+ @analyzer.stop
80
+ @table.dump(DeltaTest.config.table_file_path)
81
+ end
82
+
83
+
84
+ private
85
+
86
+ ###
87
+ # Handle exit event
88
+ ###
89
+ def hook_on_exit(&block)
90
+ at_exit do
91
+ if defined?(ParallelTests)
92
+ break unless ParallelTests.first_process?
93
+ ParallelTests.wait_for_other_processes_to_finish
94
+ end
95
+
96
+ block.call
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,88 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+
4
+ module DeltaTest
5
+ module Git
6
+ class << self
7
+
8
+ ###
9
+ # Check if in git managed directory
10
+ #
11
+ # @return {Boolean}
12
+ ###
13
+ def git_repo?
14
+ o, e, s = exec(%q{git rev-parse --is-inside-work-tree}) rescue []
15
+ !!s && s.success?
16
+ end
17
+
18
+ ###
19
+ # Get root directory of git
20
+ #
21
+ # @return {String}
22
+ ###
23
+ def root_dir
24
+ o, e, s = exec(%q{git rev-parse --show-toplevel})
25
+ s.success? ? o.strip : nil
26
+ end
27
+
28
+ ###
29
+ # Get commit id from rev name
30
+ #
31
+ # @params {String} rev - e.g., branch name
32
+ #
33
+ # @return {String}
34
+ ###
35
+ def rev_parse(rev)
36
+ o, e, s = exec(%q{git rev-parse %s}, rev)
37
+ s.success? ? o.strip : nil
38
+ end
39
+
40
+ ###
41
+ # Compare two rev names by their commit ids
42
+ #
43
+ # @params {String} r1
44
+ # @params {String} r2
45
+ #
46
+ # @return {Boolean}
47
+ ###
48
+ def same_commit?(r1, r2)
49
+ rev_parse(r1) == rev_parse(r2)
50
+ end
51
+
52
+ ###
53
+ # Get file list from git index
54
+ #
55
+ # @return {Array<String>}
56
+ ###
57
+ def ls_files
58
+ o, e, s = exec(%q{git ls-files -z})
59
+ s.success? ? o.split("\x0") : []
60
+ end
61
+
62
+ ###
63
+ # Get list of modified files in diff
64
+ #
65
+ # @params {String} base
66
+ # @params {String} head
67
+ #
68
+ # @return {Array<String>}
69
+ ###
70
+ def changed_files(base = 'master', head = 'HEAD')
71
+ o, e, s = exec(%q{git --no-pager diff --name-only -z %s %s}, base, head)
72
+ s.success? ? o.split("\x0") : []
73
+ end
74
+
75
+
76
+ private
77
+
78
+ ###
79
+ # Util for executing command
80
+ ###
81
+ def exec(command, *args)
82
+ args = args.map { |a| Shellwords.escape(a) }
83
+ Open3.capture3(command % args, chdir: DeltaTest.config.base_path)
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,64 @@
1
+ require 'set'
2
+
3
+ require_relative 'git'
4
+ require_relative 'dependencies_table'
5
+
6
+ module DeltaTest
7
+ class RelatedSpecList
8
+
9
+ attr_reader *%i[
10
+ table
11
+ changed_files
12
+ ]
13
+
14
+ ###
15
+ # Load table from the file
16
+ ###
17
+ def load_table!
18
+ unless File.exist?(DeltaTest.config.table_file_path)
19
+ raise TableNotFoundError.new(DeltaTest.config.table_file_path)
20
+ end
21
+
22
+ @table = DependenciesTable.load(DeltaTest.config.table_file_path)
23
+ end
24
+
25
+ ###
26
+ # Retrive changed files in git diff
27
+ #
28
+ # @params {String} base
29
+ # @params {String} head
30
+ ###
31
+ def retrive_changed_files!(base, head)
32
+ unless Git.git_repo?
33
+ raise NotInGitRepositoryError
34
+ end
35
+
36
+ @changed_files = Git.changed_files(base, head)
37
+ end
38
+
39
+ ###
40
+ # Calculate related spec files
41
+ #
42
+ # @return {Set<String>}
43
+ ###
44
+ def related_spec_files
45
+ spec_files = Set.new
46
+
47
+ @table.each do |spec_file, dependencies|
48
+ related = @changed_files.include?(spec_file) \
49
+ || (dependencies & @changed_files).any?
50
+
51
+ spec_files << spec_file if related
52
+ end
53
+
54
+ DeltaTest.config.custom_mappings.each do |spec_file, patterns|
55
+ if Utils.files_grep(@changed_files, patterns).any?
56
+ spec_files << spec_file
57
+ end
58
+ end
59
+
60
+ spec_files
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'generator'
2
+
3
+ module DeltaTest
4
+ module SpecHelpers
5
+
6
+ ###
7
+ # Setup generator and hook analyzer on contexts
8
+ ###
9
+ def use_delta_test(example)
10
+ $delta_test_generator ||= DeltaTest::Generator.new
11
+ $delta_test_generator.setup!
12
+
13
+ example.before(:context) do
14
+ $delta_test_generator.start!(example.metadata[:file_path])
15
+ end
16
+
17
+ example.after(:context) do
18
+ $delta_test_generator.stop!
19
+ end
20
+ end
21
+
22
+ ###
23
+ # Extend
24
+ #
25
+ # @params {} example
26
+ ###
27
+ def self.extended(example)
28
+ example.use_delta_test(example)
29
+ end
30
+
31
+ ###
32
+ # Include
33
+ # calls `extend` internally
34
+ #
35
+ # @params {} example
36
+ ###
37
+ def self.included(example)
38
+ example.extend(self)
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,93 @@
1
+ module DeltaTest
2
+ module Utils
3
+ class << self
4
+
5
+ ###
6
+ # Convert to relative and clean path
7
+ #
8
+ # @params {String|Pathname} file
9
+ # @params {Pathname} base_path
10
+ #
11
+ # @return {Pathname}
12
+ ###
13
+ def regulate_filepath(file, base_path)
14
+ file = Pathname.new(file)
15
+ file = file.relative_path_from(base_path) rescue file
16
+ file.cleanpath
17
+ end
18
+
19
+ ###
20
+ # Find file upward from pwd
21
+ #
22
+ # @params {String} file_names
23
+ #
24
+ # @return {String}
25
+ ###
26
+ def find_file_upward(*file_names)
27
+ pwd = Dir.pwd
28
+ base = Hash.new { |h, k| h[k] = pwd }
29
+ file = {}
30
+
31
+ while base.values.all? { |b| '.' != b && '/' != b }
32
+ file_names.each do |name|
33
+ file[name] = File.join(base[name], name)
34
+ base[name] = File.dirname(base[name])
35
+
36
+ return file[name] if File.exists?(file[name])
37
+ end
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ ###
44
+ # Wildcard pattern matching against a file list
45
+ #
46
+ # @params {Array<T as String|Pathname>} files
47
+ # @params {Array<String>} patterns
48
+ # @params {Array<String>} exclude_patterns
49
+ #
50
+ # @return {Array<T>}
51
+ ###
52
+ def files_grep(files, patterns = [], exclude_patterns = [])
53
+ patterns = patterns
54
+ .map { |p| grep_pattern_to_regexp(p) }
55
+ exclude_patterns = exclude_patterns
56
+ .map { |p| grep_pattern_to_regexp(p) }
57
+
58
+ any_patterns = patterns.any?
59
+ any_exclude_patterns = exclude_patterns.any?
60
+
61
+ files.select do |file|
62
+ matcher = ->(p) { p === file.to_s }
63
+
64
+ (
65
+ !any_patterns || patterns.any?(&matcher)
66
+ ) && (
67
+ !any_exclude_patterns || !exclude_patterns.any?(&matcher)
68
+ )
69
+ end
70
+ end
71
+
72
+
73
+ private
74
+
75
+ ###
76
+ # Convert file wildcard pattern to a regular expression
77
+ #
78
+ # @params {String} pattern
79
+ #
80
+ # @return {String}
81
+ ###
82
+ def grep_pattern_to_regexp(pattern)
83
+ pattern = Regexp.escape(pattern)
84
+ .gsub('\*\*/', '.*/?')
85
+ .gsub('\*\*', '.*')
86
+ .gsub('\*', '[^/]*')
87
+
88
+ Regexp.new('^%s$' % pattern)
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,9 @@
1
+ module DeltaTest
2
+
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ REVISION = 0
6
+
7
+ VERSION = [MAJOR, MINOR, REVISION].compact.join('.')
8
+
9
+ end