wtt-core 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|