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.
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