wtt-core 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +1 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +19 -0
  5. data/.travis.yml +4 -0
  6. data/Gemfile +3 -0
  7. data/README.md +36 -0
  8. data/Rakefile +3 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/coverage/.last_run.json +5 -0
  12. data/coverage/.resultset.json +857 -0
  13. data/coverage/.resultset.json.lock +0 -0
  14. data/coverage/assets/0.10.0/application.css +799 -0
  15. data/coverage/assets/0.10.0/application.js +1707 -0
  16. data/coverage/assets/0.10.0/colorbox/border.png +0 -0
  17. data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
  18. data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
  19. data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
  20. data/coverage/assets/0.10.0/favicon_green.png +0 -0
  21. data/coverage/assets/0.10.0/favicon_red.png +0 -0
  22. data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
  23. data/coverage/assets/0.10.0/loading.gif +0 -0
  24. data/coverage/assets/0.10.0/magnify.png +0 -0
  25. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  26. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  27. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  28. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  29. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  30. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  31. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  32. data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  33. data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  34. data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  35. data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  36. data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  37. data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  38. data/coverage/index.html +5396 -0
  39. data/exe/wtt_cover +28 -0
  40. data/lib/wtt/core/anchor_task.rb +30 -0
  41. data/lib/wtt/core/mapper.rb +138 -0
  42. data/lib/wtt/core/matchers/exact_matcher.rb +17 -0
  43. data/lib/wtt/core/matchers/fuzzy_matcher.rb +21 -0
  44. data/lib/wtt/core/matchers/touch_matcher.rb +17 -0
  45. data/lib/wtt/core/matchers.rb +3 -0
  46. data/lib/wtt/core/meta_data.rb +42 -0
  47. data/lib/wtt/core/paths.rb +21 -0
  48. data/lib/wtt/core/selector.rb +148 -0
  49. data/lib/wtt/core/storage.rb +89 -0
  50. data/lib/wtt/core/trace_service.rb +47 -0
  51. data/lib/wtt/core/tracer.rb +95 -0
  52. data/lib/wtt/core/version.rb +8 -0
  53. data/lib/wtt/core.rb +57 -0
  54. data/lib/wtt.rb +97 -0
  55. data/wtt.gemspec +31 -0
  56. 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,3 @@
1
+ require 'wtt/core/matchers/exact_matcher'
2
+ require 'wtt/core/matchers/fuzzy_matcher'
3
+ require 'wtt/core/matchers/touch_matcher'
@@ -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