summaryrefslogtreecommitdiff
path: root/tool/test-coverage.rb
blob: 055577feea6c886dfc8e1fab6d32de8c95715105 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
require "coverage"

Coverage.start(lines: true, branches: true, methods: true)

TEST_COVERAGE_DATA_FILE = "test-coverage.dat"

FILTER_PATHS = %w[
  lib/bundler/vendor
  lib/rubygems/resolver/molinillo
  lib/rubygems/tsort
  lib/rubygems/optparse
  tool
  test
  spec
]

def merge_coverage_data(res1, res2)
  res1.each do |path, cov1|
    cov2 = res2[path]
    if cov2
      cov1[:lines].each_with_index do |count1, i|
        next unless count1
        add_count(cov2[:lines], i, count1)
      end
      cov1[:branches].each do |base_key, targets1|
        if cov2[:branches][base_key]
          targets1.each do |target_key, count1|
            add_count(cov2[:branches][base_key], target_key, count1)
          end
        else
          cov2[:branches][base_key] = targets1
        end
      end
      cov1[:methods].each do |key, count1|
        add_count(cov2[:methods], key, count1)
      end
    else
      res2[path] = cov1
    end
  end
  res2
end

def add_count(h, key, count)
  if h[key]
    h[key] += count
  else
    h[key] = count
  end
end

def save_coverage_data(res1)
  res1.each do |_path, cov|
    if cov[:methods]
      h = {}
      cov[:methods].each do |(klass, *key), count|
        h[[klass.name, *key]] = count
      end
      cov[:methods].replace h
    end
  end
  File.open(TEST_COVERAGE_DATA_FILE, File::RDWR | File::CREAT | File::BINARY) do |f|
    f.flock(File::LOCK_EX)
    s = f.read
    res2 = s.size > 0 ? Marshal.load(s) : {}
    res1 = merge_coverage_data(res1, res2)
    f.rewind
    f << Marshal.dump(res2)
    f.flush
    f.truncate(f.pos)
  end
end

def invoke_simplecov_formatter
  # XXX docile-x.y.z and simplecov-x.y.z, simplecov-html-x.y.z, simplecov_json_formatter-x.y.z
  %w[simplecov simplecov-html simplecov_json_formatter docile].each do |f|
    Dir.glob("#{__dir__}/../.bundle/gems/#{f}-*/lib").each do |d|
      $LOAD_PATH.unshift d
    end
  end

  require "simplecov"
  res = Marshal.load(File.binread(TEST_COVERAGE_DATA_FILE))
  simplecov_result = {}
  base_dir = File.dirname(__dir__)
  cur_dir = Dir.pwd

  res.each do |path, cov|
    next unless path.start_with?(base_dir) || path.start_with?(cur_dir)
    next if FILTER_PATHS.any? {|dir| path.start_with?(File.join(base_dir, dir))}
    simplecov_result[path] = cov
  end

  a, b = base_dir, cur_dir
  until a == b
    if a.size > b.size
      a = File.dirname(a)
    else
      b = File.dirname(b)
    end
  end
  root_dir = a

  SimpleCov.configure do
    root(root_dir)
    coverage_dir(File.join(cur_dir, "coverage"))
  end
  res = SimpleCov::Result.new(simplecov_result)
  res.command_name = "Ruby's `make test-all`"
  SimpleCov::Formatter::HTMLFormatter.new.format(res)
end

pid = $$
pwd = Dir.pwd

at_exit do
  exit_exc = $!

  Dir.chdir(pwd) do
    save_coverage_data(Coverage.result)
    if pid == $$
      begin
        nil while Process.waitpid(-1)
      rescue Errno::ECHILD
        invoke_simplecov_formatter
      end
    end
  end

  raise exit_exc if exit_exc
end