wtt-core 0.1.15
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 +7 -0
- data/.bundle/config +1 -0
- data/.rspec +2 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/README.md +36 -0
- data/Rakefile +3 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/coverage/.last_run.json +5 -0
- data/coverage/.resultset.json +857 -0
- data/coverage/.resultset.json.lock +0 -0
- data/coverage/assets/0.10.0/application.css +799 -0
- data/coverage/assets/0.10.0/application.js +1707 -0
- data/coverage/assets/0.10.0/colorbox/border.png +0 -0
- data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
- data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
- data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.10.0/favicon_green.png +0 -0
- data/coverage/assets/0.10.0/favicon_red.png +0 -0
- data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
- data/coverage/assets/0.10.0/loading.gif +0 -0
- data/coverage/assets/0.10.0/magnify.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +5396 -0
- data/exe/wtt_cover +28 -0
- data/lib/wtt/core/anchor_task.rb +30 -0
- data/lib/wtt/core/mapper.rb +138 -0
- data/lib/wtt/core/matchers/exact_matcher.rb +17 -0
- data/lib/wtt/core/matchers/fuzzy_matcher.rb +21 -0
- data/lib/wtt/core/matchers/touch_matcher.rb +17 -0
- data/lib/wtt/core/matchers.rb +3 -0
- data/lib/wtt/core/meta_data.rb +42 -0
- data/lib/wtt/core/paths.rb +21 -0
- data/lib/wtt/core/selector.rb +148 -0
- data/lib/wtt/core/storage.rb +89 -0
- data/lib/wtt/core/trace_service.rb +47 -0
- data/lib/wtt/core/tracer.rb +95 -0
- data/lib/wtt/core/version.rb +8 -0
- data/lib/wtt/core.rb +57 -0
- data/lib/wtt.rb +97 -0
- data/wtt.gemspec +31 -0
- metadata +210 -0
data/exe/wtt_cover
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'wtt'
|
3
|
+
require 'simplecov'
|
4
|
+
require 'simplecov-html'
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
6
|
+
SimpleCov.command_name 'WTT'
|
7
|
+
SimpleCov.coverage_dir('wtt_coverage')
|
8
|
+
|
9
|
+
repo = Rugged::Repository.discover(Dir.pwd)
|
10
|
+
storage = WTT::Core::Storage.new( repo )
|
11
|
+
mapper = WTT::Core::Mapper.new( storage )
|
12
|
+
|
13
|
+
coverage = Hash.new { |h, k| h[k] = [] }
|
14
|
+
|
15
|
+
mapper.mapping.each do |_test, test_coverage|
|
16
|
+
|
17
|
+
test_coverage.each do |file, lines|
|
18
|
+
source_file = "#{WTT::Core.rake_root}/#{file}"
|
19
|
+
lines.each do |line_no|
|
20
|
+
old_count = coverage[source_file][line_no - 1].to_i
|
21
|
+
coverage[source_file][line_no - 1] = old_count + 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
result = SimpleCov::Result.new(coverage)
|
27
|
+
result.format!
|
28
|
+
puts 'WTT coverage report saved to ./wtt_coverage'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'wtt/core'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
# Top level namespace for What to Test
|
5
|
+
module WTT
|
6
|
+
# Functionality core to WTT belongs here
|
7
|
+
module Core
|
8
|
+
# Helper to declare take tasks for anchor raise/drop.
|
9
|
+
class AnchorTasks
|
10
|
+
include Rake::DSL
|
11
|
+
def initialize
|
12
|
+
define_anchor_tasks
|
13
|
+
end
|
14
|
+
|
15
|
+
def define_anchor_tasks
|
16
|
+
namespace :wtt do
|
17
|
+
desc 'Set the SHA for use in WTT'
|
18
|
+
task 'anchor_drop' do
|
19
|
+
WTT::Core.anchor_drop
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Clear the SHA for use in WTT'
|
23
|
+
task 'anchor_raise' do
|
24
|
+
WTT::Core.anchor_raise
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'wtt'
|
2
|
+
require 'rugged'
|
3
|
+
require 'json'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
# Top level namespace for What to Test
|
7
|
+
module WTT
|
8
|
+
# Functionality core to WTT belongs here
|
9
|
+
module Core
|
10
|
+
# Mapping from test file to executed code (i.e. coverage without execution count).
|
11
|
+
#
|
12
|
+
# Terminologies:
|
13
|
+
# spectra: { filename => [line, numbers, executed], ... }
|
14
|
+
# mapping: { test_file => spectra }
|
15
|
+
class Mapper
|
16
|
+
STORAGE_SECTION = 'mapping'.freeze
|
17
|
+
|
18
|
+
attr_reader :mapping
|
19
|
+
|
20
|
+
# @param storage [WTT::Core::Storage] The storage object to read and write from.
|
21
|
+
def initialize(storage)
|
22
|
+
@storage = storage
|
23
|
+
read!
|
24
|
+
end
|
25
|
+
|
26
|
+
def matcher
|
27
|
+
@matcher ||= WTT.configuration.matcher
|
28
|
+
end
|
29
|
+
|
30
|
+
# Append the new mapping to test-to-code mapping file.
|
31
|
+
#
|
32
|
+
# @param test [String] test file for which the coverage data is produced
|
33
|
+
# @param coverage [Hash] coverage data generated using `Coverage.start` and `Coverage.result`
|
34
|
+
# @return [void]
|
35
|
+
def append_from_coverage(test, coverage)
|
36
|
+
spectra = normalize_paths(select_project_files(spectra_from_coverage(coverage)))
|
37
|
+
@mapping[test] = spectra
|
38
|
+
end
|
39
|
+
|
40
|
+
# Append the new mapping to test-to-code mapping file.
|
41
|
+
#
|
42
|
+
# @param test [String] test file for which the coverage data is produced
|
43
|
+
# @param coverage [Hash] coverage data generated using `Coverage.start` and `Coverage.result`
|
44
|
+
# @return [void]
|
45
|
+
def append_from_simplecov(test, coverage)
|
46
|
+
spectra = normalize_paths(select_project_files(spectra_from_simplecov(coverage)))
|
47
|
+
@mapping[test] = spectra
|
48
|
+
end
|
49
|
+
|
50
|
+
# Read test-to-code mapping from storage.
|
51
|
+
def read!
|
52
|
+
@mapping = @storage.read(STORAGE_SECTION)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Write test-to-code mapping to storage.
|
56
|
+
def write!
|
57
|
+
@storage.write!(STORAGE_SECTION, @mapping)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get tests affected from change of file `file` at line number `lineno`
|
61
|
+
#
|
62
|
+
# @param file [String] file name which might have effects on some tests
|
63
|
+
# @param lineno [Integer] line number in the file which might have effects on some tests
|
64
|
+
# @return [Set] a set of test files which might be affected by the change in file at lineno
|
65
|
+
def get_tests(file, lineno)
|
66
|
+
tests = Set.new
|
67
|
+
@mapping.each do |test, spectra|
|
68
|
+
lines = spectra[file]
|
69
|
+
next unless lines
|
70
|
+
tests << test if matcher.match(lines, lineno)
|
71
|
+
end
|
72
|
+
warn "No tests found for #{file}:#{lineno}." if file.end_with?( '.rb' )
|
73
|
+
tests
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Convert absolute path to relative path from the project (Git repository) root.
|
79
|
+
#
|
80
|
+
# @param file [String] file name (absolute path)
|
81
|
+
# @return [String] normalized file path
|
82
|
+
def normalized_path(file)
|
83
|
+
File.expand_path(file).sub("#{WTT::Core.rake_root}/", '')
|
84
|
+
end
|
85
|
+
|
86
|
+
# Normalize all file names in a spectra.
|
87
|
+
#
|
88
|
+
# @param spectra [Hash] spectra data
|
89
|
+
# @return [Hash] spectra whose keys (file names) are normalized
|
90
|
+
def normalize_paths(spectra)
|
91
|
+
Hash[spectra.map { |k, v| [normalized_path(k), v] }]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Filter out the files outside of the target project using file path.
|
95
|
+
#
|
96
|
+
# @param spectra [Hash] spectra data
|
97
|
+
# @return [Hash] spectra with only files inside the target project
|
98
|
+
def select_project_files(spectra)
|
99
|
+
spectra.select do |filename, _lines|
|
100
|
+
filename.start_with?(WTT::Core.rake_root)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Generate spectra data from Ruby coverage library's data
|
105
|
+
#
|
106
|
+
# @param cov [Hash] coverage data generated using `Coverage.result`
|
107
|
+
# @return [Hash] spectra data
|
108
|
+
def spectra_from_coverage(cov)
|
109
|
+
spectra = Hash.new { |h, k| h[k] = [] }
|
110
|
+
cov.each do |filename, executions|
|
111
|
+
|
112
|
+
executions.each_with_index do |execution, i|
|
113
|
+
next if execution.nil? || execution == 0
|
114
|
+
spectra[filename] << i + 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
spectra
|
118
|
+
end
|
119
|
+
|
120
|
+
# Generate spectra data from Ruby coverage library's data
|
121
|
+
#
|
122
|
+
# @param cov [Hash] coverage data generated using `Simplecov.result`
|
123
|
+
# @return [Hash] spectra data
|
124
|
+
def spectra_from_simplecov(cov)
|
125
|
+
|
126
|
+
spectra = Hash.new { |h, k| h[k] = [] }
|
127
|
+
cov.files.each do |file|
|
128
|
+
filename = file.filename
|
129
|
+
file.coverage.each_with_index do |execution, i|
|
130
|
+
next if execution.nil? || execution == 0
|
131
|
+
spectra[filename] << i + 1
|
132
|
+
end
|
133
|
+
end
|
134
|
+
spectra
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'wtt/core'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
# Top level namespace for What to Test
|
5
|
+
module WTT
|
6
|
+
# Functionality core to WTT belongs here
|
7
|
+
module Core
|
8
|
+
module Matchers
|
9
|
+
# Matches the extact line that changed
|
10
|
+
class Exact
|
11
|
+
def match(spectra, lineno)
|
12
|
+
true if spectra.include?(lineno) || lineno == 0
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'wtt/core'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
# Top level namespace for What to Test
|
5
|
+
module WTT
|
6
|
+
# Functionality core to WTT belongs here
|
7
|
+
module Core
|
8
|
+
module Matchers
|
9
|
+
# Matches if the line that changed is within "spread" distance
|
10
|
+
class Fuzzy
|
11
|
+
def initialize(spread=11)
|
12
|
+
@spread = spread
|
13
|
+
end
|
14
|
+
|
15
|
+
def match(spectra, lineno)
|
16
|
+
true if spectra.any? { |v| (v - lineno).abs <= @spread } || lineno == 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'wtt/core'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
# Top level namespace for What to Test
|
5
|
+
module WTT
|
6
|
+
# Functionality core to WTT belongs here
|
7
|
+
module Core
|
8
|
+
module Matchers
|
9
|
+
# Matches the extact line that changed
|
10
|
+
class Touch
|
11
|
+
def match(spectra, lineno)
|
12
|
+
true if (spectra.first <= lineno && lineno <= spectra.last) || lineno == 0
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'wtt'
|
2
|
+
|
3
|
+
# Top level namespace for What to Test
|
4
|
+
module WTT
|
5
|
+
# Functionality core to WTT belongs here
|
6
|
+
module Core
|
7
|
+
# Bucket for holding data in the wtt file
|
8
|
+
class MetaData
|
9
|
+
STORAGE_SECTION = 'meta'
|
10
|
+
|
11
|
+
# @param storage [Storage] Where to read//write
|
12
|
+
def initialize(storage)
|
13
|
+
@storage = storage
|
14
|
+
read!
|
15
|
+
end
|
16
|
+
|
17
|
+
def anchored_commit
|
18
|
+
@data['anchored_commit']
|
19
|
+
end
|
20
|
+
|
21
|
+
def anchored_commit=( val )
|
22
|
+
@data['anchored_commit'] = val
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](name)
|
26
|
+
@data[name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def []=(name, value)
|
30
|
+
@data[name] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def read!
|
34
|
+
@data = @storage.read(STORAGE_SECTION)
|
35
|
+
end
|
36
|
+
|
37
|
+
def write!
|
38
|
+
@storage.write!(STORAGE_SECTION, @data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
# Top level namespace for What to Test
|
4
|
+
module WTT
|
5
|
+
# Functionality core to WTT belongs here
|
6
|
+
module Core
|
7
|
+
class << self
|
8
|
+
def rake_root
|
9
|
+
@@root_dir ||= Rake.application.find_rakefile_location[1].freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def rake_root=(dir)
|
13
|
+
@@root_dir = dir
|
14
|
+
end
|
15
|
+
|
16
|
+
def wtt_root
|
17
|
+
"#{rake_root}/.wtt".freeze
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'wtt'
|
2
|
+
require 'rugged'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# Top level namespace for What to Test
|
6
|
+
module WTT
|
7
|
+
# Functionality core to WTT belongs here
|
8
|
+
module Core
|
9
|
+
# Select tests using Git information and a {Mapper}.
|
10
|
+
class Selector
|
11
|
+
attr_reader :tests
|
12
|
+
attr_writer :walker, :mapping
|
13
|
+
|
14
|
+
# @param opts [Hash] a hash with options
|
15
|
+
def initialize(opts)
|
16
|
+
@repo = opts[:repo]
|
17
|
+
@metadata = opts[:meta_data]
|
18
|
+
@walker = opts[:walker]
|
19
|
+
@test_files = opts[:test_files]
|
20
|
+
@mapping = opts[:mapping]
|
21
|
+
@target_revision = @repo.lookup(opts[:target_sha]) if opts[:target_sha]
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Select tests using differences in anchored commit and target commit
|
26
|
+
# (or current working tree) and {TestToCodeMapping}.
|
27
|
+
#
|
28
|
+
# @return [Set] a set of tests that might be affected by changes in base_sha...target_sha
|
29
|
+
def select_tests!
|
30
|
+
# Base should be the commit `anchor` has run on.
|
31
|
+
# NOT the one test-to-code mapping was committed to.
|
32
|
+
@base_obj = anchored_commit
|
33
|
+
|
34
|
+
# select all tests if anchored commit does not exist
|
35
|
+
return Set.new(@test_files) unless @base_obj
|
36
|
+
|
37
|
+
change_count = 0
|
38
|
+
|
39
|
+
@tests = Set.new
|
40
|
+
diff.each_patch do |patch|
|
41
|
+
change_count += 1
|
42
|
+
file = patch.delta.old_file[:path]
|
43
|
+
if test_file?(file)
|
44
|
+
@tests << file
|
45
|
+
else
|
46
|
+
select_tests_from_patch(patch)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@tests.delete(nil)
|
50
|
+
puts "WTT found #{@tests.count} tests for #{change_count} changes."
|
51
|
+
@tests
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def diff
|
57
|
+
opts = {
|
58
|
+
include_untracked: true,
|
59
|
+
recurse_untracked_dirs: true
|
60
|
+
}
|
61
|
+
defined?(@target_revision) ? @base_obj.diff(@target_revision, opts) : @base_obj.diff_workdir(opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
def mapping
|
65
|
+
@mapping ||= begin
|
66
|
+
sha = defined?(@target_revision) && !@target_revision.nil? ? @target_revision.oid : @repo.head.target_id
|
67
|
+
Mapper.new(Storage.new(@repo, sha))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Select tests which are affected by the change of given patch.
|
72
|
+
#
|
73
|
+
# @param patch [Rugged::Patch]
|
74
|
+
# @return [Set] set of selected tests
|
75
|
+
def select_tests_from_patch(patch)
|
76
|
+
target_lines = Set.new
|
77
|
+
file = patch.delta.old_file[:path]
|
78
|
+
|
79
|
+
|
80
|
+
patch.each_hunk do |hunk|
|
81
|
+
target_lines.merge target_lines_from_hunk(hunk)
|
82
|
+
end
|
83
|
+
|
84
|
+
target_lines.each do |line|
|
85
|
+
@tests += mapping.get_tests(file, line)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Find lines within a hunk
|
90
|
+
#
|
91
|
+
# @param hunk [Rugged::Hunk]
|
92
|
+
# @return [Array] Lines that changed
|
93
|
+
def target_lines_from_hunk(hunk)
|
94
|
+
target_lines = []
|
95
|
+
prev_line = nil
|
96
|
+
hunk.each_line do |line|
|
97
|
+
line_no = hunk_line_no(line, prev_line, hunk)
|
98
|
+
target_lines << line_no unless line_no.nil?
|
99
|
+
prev_line = line
|
100
|
+
end
|
101
|
+
target_lines
|
102
|
+
end
|
103
|
+
|
104
|
+
# Figure out the line number for a change
|
105
|
+
#
|
106
|
+
# @param line [Rugged::Line]
|
107
|
+
# @param prev_line [Rugged::Line]
|
108
|
+
# @param hunk [Rugged::Hunk]
|
109
|
+
# @return [int] A line number or nil
|
110
|
+
def hunk_line_no(line, prev_line, hunk)
|
111
|
+
case line.line_origin
|
112
|
+
when :addition
|
113
|
+
if prev_line && !prev_line.addition?
|
114
|
+
return prev_line.old_lineno
|
115
|
+
elsif prev_line.nil?
|
116
|
+
return hunk.old_start
|
117
|
+
end
|
118
|
+
when :deletion
|
119
|
+
return line.old_lineno
|
120
|
+
end
|
121
|
+
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def walker
|
126
|
+
@walker ||= Rugged::Walker.new(@repo)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Find the commit `anchor` has been run on, or the previous commit.
|
130
|
+
def anchored_commit
|
131
|
+
return @repo.lookup(@metadata.anchored_commit) if @metadata.anchored_commit
|
132
|
+
walker.sorting(Rugged::SORT_DATE)
|
133
|
+
walker.push(@repo.head.target)
|
134
|
+
commit = walker.find do |c|
|
135
|
+
c.parents.size == 1
|
136
|
+
end
|
137
|
+
@repo.lookup(commit.oid)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Check if the given file is a test file.
|
141
|
+
#
|
142
|
+
# @param file_from_mapping [String]
|
143
|
+
def test_file?(file_from_mapping)
|
144
|
+
@test_files.any? { |f| file_from_mapping.include?(f) }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rugged'
|
2
|
+
|
3
|
+
module WTT
|
4
|
+
module Core
|
5
|
+
# A utility class to store WTT data such as test-to-code mapping and metadata.
|
6
|
+
class Storage
|
7
|
+
# Initialize the storage from given repo and sha. This reads contents from
|
8
|
+
# a `.wtt` file. When sha is not nil, contents of the file on that commit
|
9
|
+
# is read. Data can be written only when sha is nil (written to current
|
10
|
+
# working tree).
|
11
|
+
#
|
12
|
+
# @param repo [Rugged::Repository]
|
13
|
+
# @param sha [String] sha of the commit which data should be read from.
|
14
|
+
# nil means reading from/writing to current working tree.
|
15
|
+
def initialize(repo = nil, sha = nil)
|
16
|
+
@repo = repo
|
17
|
+
@sha = sha
|
18
|
+
end
|
19
|
+
|
20
|
+
# Read data from the storage in the given section.
|
21
|
+
#
|
22
|
+
# @param section [String]
|
23
|
+
# @return [Hash]
|
24
|
+
def read(section)
|
25
|
+
data_from_str(read_storage_content)[section] || {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Write value to the given section in the storage.
|
29
|
+
# Locks the file so that concurrent write does not occur.
|
30
|
+
#
|
31
|
+
# @param section [String]
|
32
|
+
# @param value [Hash]
|
33
|
+
# rubocop:disable Metrics/AbcSize
|
34
|
+
def write!(section, value)
|
35
|
+
fail 'Data cannot be written to the storage back in git history' unless @sha.nil?
|
36
|
+
File.open(WTT::Core.wtt_root, File::RDWR | File::CREAT, 0644) do |f|
|
37
|
+
f.flock(File::LOCK_EX)
|
38
|
+
data = data_from_str( f.read )
|
39
|
+
data[section] = value
|
40
|
+
f.rewind
|
41
|
+
f.write(data.to_json)
|
42
|
+
f.flush
|
43
|
+
f.truncate(f.pos)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# rubocop:enable Metrics/AbcSize
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def data_from_str(str)
|
51
|
+
str.length > 0 ? JSON.parse(str) : {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def filename_from_repository_root
|
55
|
+
WTT::Core.wtt_root.gsub(@repo.workdir, '')
|
56
|
+
end
|
57
|
+
|
58
|
+
def storage_file_oid
|
59
|
+
paths = filename_from_repository_root.split(File::SEPARATOR)
|
60
|
+
obj = find_file_in_repo paths
|
61
|
+
return nil unless obj
|
62
|
+
obj[:oid]
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_file_in_repo(paths)
|
66
|
+
tree = @repo.lookup(@sha).tree
|
67
|
+
dirs = paths[0...-1]
|
68
|
+
filename = paths[-1]
|
69
|
+
|
70
|
+
dirs.each do |dir|
|
71
|
+
obj = tree[dir]
|
72
|
+
return nil unless obj
|
73
|
+
tree = @repo.lookup(obj[:oid])
|
74
|
+
end
|
75
|
+
tree[filename]
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def read_storage_content
|
80
|
+
if @sha
|
81
|
+
oid = storage_file_oid
|
82
|
+
oid.nil? ? '' : @repo.lookup(oid).content
|
83
|
+
else
|
84
|
+
File.exist?(WTT::Core.wtt_root) ? File.read(WTT::Core.wtt_root) : ''
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
# Top level namespace for What to Test
|
3
|
+
module WTT
|
4
|
+
# Functionality core to WTT belongs here
|
5
|
+
module Core
|
6
|
+
# Service for gathering coverage information and sending it back via DRB.
|
7
|
+
class TraceService
|
8
|
+
attr_reader :coverage
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@coverage = Hash.new { |h, k| h[k] = [] }
|
12
|
+
@trace = TracePoint.new(:line) do |tp|
|
13
|
+
if should_include_file?( tp.path )
|
14
|
+
old_count = @coverage[tp.path][tp.lineno].to_i
|
15
|
+
@coverage[tp.path][tp.lineno] = old_count + 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_trace
|
21
|
+
reset_coverage
|
22
|
+
@trace.enable
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop_trace
|
26
|
+
@trace.disable
|
27
|
+
end
|
28
|
+
|
29
|
+
def coverage
|
30
|
+
stop_trace
|
31
|
+
results = @coverage.dup
|
32
|
+
start_trace
|
33
|
+
return results
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def reset_coverage
|
39
|
+
@coverage = Hash.new { |h, k| h[k] = [] }
|
40
|
+
end
|
41
|
+
|
42
|
+
def should_include_file?(path)
|
43
|
+
!WTT.configuration.reject_filters.any? {|f| f.match(path)}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'wtt/core'
|
2
|
+
require 'drb/drb'
|
3
|
+
|
4
|
+
# Top level namespace for What to Test
|
5
|
+
module WTT
|
6
|
+
# Functionality core to WTT belongs here
|
7
|
+
module Core
|
8
|
+
# Helper class to gather coverage data via the Tracepoint API
|
9
|
+
class Tracer
|
10
|
+
def initialize
|
11
|
+
@remotes = []
|
12
|
+
reset_coverage
|
13
|
+
connect_remotes WTT.configuration.remotes
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_trace
|
17
|
+
@debug = []
|
18
|
+
reset_coverage
|
19
|
+
start_remotes
|
20
|
+
tracepoint.enable
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop_trace
|
24
|
+
stop_remotes
|
25
|
+
tracepoint.disable
|
26
|
+
end
|
27
|
+
|
28
|
+
def coverage
|
29
|
+
all_coverage = @coverage.dup
|
30
|
+
@remotes.each { |r| merge_remote_coverage!(all_coverage, r.coverage) }
|
31
|
+
all_coverage
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def merge_remote_coverage!(local, remote)
|
37
|
+
remote.each do |filename, remote_coverage|
|
38
|
+
if local.has_key? (filename)
|
39
|
+
remote_coverage.each_with_index do |v, i |
|
40
|
+
# We have to do this dance because the arrays contain nils
|
41
|
+
current = local[filename][i].to_i
|
42
|
+
local[filename][i] = current + v.to_i
|
43
|
+
end
|
44
|
+
else
|
45
|
+
local[filename] = remote_coverage.dup
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def start_remotes
|
52
|
+
@remotes.each { |r| r.start_trace }
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop_remotes
|
56
|
+
@remotes.each { |r| r.stop_trace }
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_coverage
|
60
|
+
@coverage = Hash.new { |h, k| h[k] = [] }
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_include_file?(path)
|
64
|
+
!WTT.configuration.reject_filters.any? {|f| f.match(path)}
|
65
|
+
end
|
66
|
+
|
67
|
+
def tracepoint
|
68
|
+
@trace ||= TracePoint.new(:line, :call, :return) do |tp|
|
69
|
+
|
70
|
+
if should_include_file?( tp.path )
|
71
|
+
old_count = @coverage[tp.path][tp.lineno].to_i
|
72
|
+
@coverage[tp.path][tp.lineno] = old_count + 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def connect_remotes(remote_uris)
|
78
|
+
remote_uris.each do |remote_uri|
|
79
|
+
connect_remote remote_uri
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def connect_remote(remote_uri)
|
84
|
+
begin
|
85
|
+
service = DRbObject.new_with_uri(remote_uri)
|
86
|
+
# Ask for the (empty) coverage to see if the service is live
|
87
|
+
service.coverage
|
88
|
+
@remotes << service
|
89
|
+
rescue Exception => ex
|
90
|
+
warn "Could not connect to Traced at #{remote_uri}. #{ex.message}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|