turbulence 0.0.3 → 0.0.4
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.
- data/Gemfile +4 -0
- data/Gemfile.lock +11 -1
- data/bin/bule +3 -29
- data/lib/turbulence.rb +20 -42
- data/lib/turbulence/calculators/churn.rb +40 -0
- data/lib/turbulence/calculators/complexity.rb +33 -0
- data/lib/turbulence/command_line_interface.rb +37 -0
- data/lib/turbulence/scatter_plot_generator.rb +10 -12
- data/lib/turbulence/version.rb +1 -1
- data/spec/turbulence/calculators/churn_spec.rb +103 -0
- data/spec/turbulence/calculators/complexity_spec.rb +37 -0
- data/template/turbulence.html +8 -7
- metadata +11 -5
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
turbulence (0.0.
|
4
|
+
turbulence (0.0.3)
|
5
5
|
flog (= 2.5.0)
|
6
6
|
json (= 1.4.6)
|
7
7
|
launchy (~> 0.4.0)
|
@@ -10,6 +10,7 @@ GEM
|
|
10
10
|
remote: http://rubygems.org/
|
11
11
|
specs:
|
12
12
|
configuration (1.2.0)
|
13
|
+
diff-lcs (1.1.2)
|
13
14
|
flog (2.5.0)
|
14
15
|
ruby_parser (~> 2.0)
|
15
16
|
sexp_processor (~> 3.0)
|
@@ -18,6 +19,14 @@ GEM
|
|
18
19
|
configuration (>= 0.0.5)
|
19
20
|
rake (>= 0.8.1)
|
20
21
|
rake (0.8.7)
|
22
|
+
rspec (2.5.0)
|
23
|
+
rspec-core (~> 2.5.0)
|
24
|
+
rspec-expectations (~> 2.5.0)
|
25
|
+
rspec-mocks (~> 2.5.0)
|
26
|
+
rspec-core (2.5.1)
|
27
|
+
rspec-expectations (2.5.0)
|
28
|
+
diff-lcs (~> 1.1.2)
|
29
|
+
rspec-mocks (2.5.0)
|
21
30
|
ruby_parser (2.0.6)
|
22
31
|
sexp_processor (~> 3.0)
|
23
32
|
sexp_processor (3.0.5)
|
@@ -29,4 +38,5 @@ DEPENDENCIES
|
|
29
38
|
flog
|
30
39
|
json
|
31
40
|
launchy (~> 0.4.0)
|
41
|
+
rspec
|
32
42
|
turbulence!
|
data/bin/bule
CHANGED
@@ -1,32 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'turbulence'
|
3
|
-
require 'turbulence/scatter_plot_generator'
|
4
|
-
require 'fileutils'
|
5
|
-
require 'launchy'
|
6
|
-
TURBULENCE_PATH = File.join(File.absolute_path(File.dirname(__FILE__)), "..")
|
7
3
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def copy_templates_into(directory)
|
13
|
-
FileUtils.cp path_to_template('turbulence.html'), directory
|
14
|
-
FileUtils.cp path_to_template('highcharts.js'), directory
|
15
|
-
FileUtils.cp path_to_template('jquery.min.js'), directory
|
16
|
-
end
|
17
|
-
|
18
|
-
def generate_bundle_for(git_repo)
|
19
|
-
FileUtils.mkdir_p("turbulence")
|
20
|
-
Dir.chdir("turbulence") do
|
21
|
-
copy_templates_into(Dir.pwd)
|
22
|
-
File.open("cc.js", "w") do |f|
|
23
|
-
f.write Turbulence::ScatterPlotGenerator.from(Turbulence.new(git_repo).metrics)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def open_bundle(directory)
|
29
|
-
Launchy.open("file://#{directory}/turbulence/turbulence.html")
|
30
|
-
end
|
31
|
-
generate_bundle_for Dir.pwd
|
32
|
-
open_bundle Dir.pwd
|
4
|
+
cli = Turbulence::CommandLineInterface.new(Dir.pwd)
|
5
|
+
cli.generate_bundle
|
6
|
+
cli.open_bundle
|
data/lib/turbulence.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'turbulence/scatter_plot_generator'
|
2
|
+
require 'turbulence/command_line_interface'
|
3
|
+
require 'turbulence/calculators/churn'
|
4
|
+
require 'turbulence/calculators/complexity'
|
3
5
|
|
4
6
|
class Turbulence
|
5
|
-
class Reporter < StringIO
|
6
|
-
def average
|
7
|
-
Float(string.scan(/^\s+([^:]+).*total$/).flatten.first)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
7
|
attr_reader :dir
|
12
8
|
attr_reader :metrics
|
13
9
|
def initialize(dir)
|
@@ -19,52 +15,34 @@ class Turbulence
|
|
19
15
|
end
|
20
16
|
end
|
21
17
|
|
22
|
-
def
|
18
|
+
def files_of_interest
|
23
19
|
files = ["app/models", "app/controllers", "app/helpers", "lib"].map{|base_dir| "#{base_dir}/**/*\.rb"}
|
24
20
|
@ruby_files ||= Dir[*files]
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
28
|
-
|
29
|
-
files.each do |count, filename|
|
30
|
-
print "."
|
31
|
-
metrics_for(filename)[:churn] = Integer(count)
|
32
|
-
end
|
23
|
+
def complexity
|
24
|
+
calculate_metrics Turbulence::Calculators::Complexity
|
33
25
|
end
|
34
26
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
print "."
|
27
|
+
def churn
|
28
|
+
calculate_metrics Turbulence::Calculators::Churn
|
29
|
+
end
|
39
30
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
metrics_for(filename)[:complexity] = reporter.average
|
45
|
-
rescue SyntaxError, Racc::ParseError => e
|
46
|
-
puts "\nError flogging: #{filename}\n"
|
47
|
-
end
|
31
|
+
def calculate_metrics(calculator)
|
32
|
+
puts "calculating metric: #{calculator}"
|
33
|
+
calculator.for_these_files(files_of_interest) do |filename, score|
|
34
|
+
set_file_metric(filename, calculator, score)
|
48
35
|
end
|
36
|
+
puts "\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_file_metric(filename, metric, value)
|
40
|
+
print "."
|
41
|
+
metrics_for(filename)[metric] = value
|
49
42
|
end
|
50
43
|
|
51
44
|
def metrics_for(filename)
|
52
45
|
@metrics[filename] ||= {}
|
53
46
|
end
|
54
47
|
|
55
|
-
private
|
56
|
-
def changes_by_ruby_file
|
57
|
-
line_changes_by_file.select do |count, filename|
|
58
|
-
filename =~ /\.rb$/ && File.exist?(filename)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def line_changes_by_file
|
63
|
-
`git log --all -M -C --numstat --format="%n"`.each_line.reject{|line| line =~ /^\n$/}.map do |line|
|
64
|
-
adds, deletes, filename = line.chomp.split(/\t/)
|
65
|
-
[filename, adds.to_i + deletes.to_i]
|
66
|
-
end.group_by(&:first).map do |filename, stats|
|
67
|
-
[stats.map(&:last).tap{|list| list.pop}.inject(0){|n, i| n + i}, filename]
|
68
|
-
end
|
69
|
-
end
|
70
48
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Turbulence
|
2
|
+
module Calculators
|
3
|
+
class Churn
|
4
|
+
class << self
|
5
|
+
def for_these_files(files)
|
6
|
+
changes_by_ruby_file.select do |count, filename|
|
7
|
+
yield filename, count if files.include?(filename)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def changes_by_ruby_file
|
12
|
+
ruby_files_changed_in_git.group_by(&:first).map do |filename, stats|
|
13
|
+
[stats.map(&:last).tap{|list| list.pop}.inject(0){|n, i| n + i}, filename]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def counted_line_changes_by_file_by_commit
|
18
|
+
git_log_file_lines.map do |line|
|
19
|
+
adds, deletes, filename = line.split(/\t/)
|
20
|
+
[filename, adds.to_i + deletes.to_i]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ruby_files_changed_in_git
|
25
|
+
counted_line_changes_by_file_by_commit.select do |filename, count|
|
26
|
+
filename =~ /\.rb$/ && File.exist?(filename)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def git_log_file_lines
|
31
|
+
git_log_command.each_line.reject{|line| line =~ /^\n$/}.map(&:chomp)
|
32
|
+
end
|
33
|
+
|
34
|
+
def git_log_command
|
35
|
+
`git log --all -M -C --numstat --format="%n"`
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'flog'
|
3
|
+
class Turbulence
|
4
|
+
module Calculators
|
5
|
+
class Complexity
|
6
|
+
class << self
|
7
|
+
def flogger
|
8
|
+
@flogger ||= Flog.new
|
9
|
+
end
|
10
|
+
def for_these_files(files)
|
11
|
+
files.each do |filename|
|
12
|
+
yield filename, score_for_file(filename)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def score_for_file(filename)
|
17
|
+
flogger.flog filename
|
18
|
+
reporter = Reporter.new
|
19
|
+
flogger.report(reporter)
|
20
|
+
reporter.score
|
21
|
+
rescue SyntaxError, Racc::ParseError
|
22
|
+
STDERR.puts "\nError flogging: #{filename}\n"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Reporter < ::StringIO
|
27
|
+
def score
|
28
|
+
Float(string.scan(/^\s+([^:]+).*total$/).flatten.first)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'launchy'
|
3
|
+
|
4
|
+
class Turbulence
|
5
|
+
class CommandLineInterface
|
6
|
+
TURBULENCE_PATH = File.join(File.absolute_path(File.dirname(__FILE__)), "..", "..")
|
7
|
+
|
8
|
+
attr_reader :directory
|
9
|
+
def initialize(directory)
|
10
|
+
@directory = directory
|
11
|
+
end
|
12
|
+
|
13
|
+
def path_to_template(filename)
|
14
|
+
File.join(TURBULENCE_PATH, "template", filename)
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_templates_into(directory)
|
18
|
+
['turbulence.html', 'highcharts.js', 'jquery.min.js'].each do |filename|
|
19
|
+
FileUtils.cp path_to_template(filename), directory
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_bundle
|
24
|
+
FileUtils.mkdir_p("turbulence")
|
25
|
+
Dir.chdir("turbulence") do
|
26
|
+
copy_templates_into(Dir.pwd)
|
27
|
+
File.open("cc.js", "w") do |f|
|
28
|
+
f.write Turbulence::ScatterPlotGenerator.from(Turbulence.new(directory).metrics).to_js
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def open_bundle
|
34
|
+
Launchy.open("file://#{directory}/turbulence/turbulence.html")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,14 +2,14 @@ require 'json'
|
|
2
2
|
class Turbulence
|
3
3
|
class ScatterPlotGenerator
|
4
4
|
def self.from(metrics_hash)
|
5
|
+
new(metrics_hash)
|
6
|
+
end
|
7
|
+
attr_reader :metrics_hash
|
8
|
+
def initialize(metrics_hash)
|
9
|
+
@metrics_hash = metrics_hash
|
10
|
+
end
|
5
11
|
|
6
|
-
|
7
|
-
{:filename => filename, :x => metrics[:churn], :y => metrics[:complexity]}
|
8
|
-
end.reject do |metrics|
|
9
|
-
metrics[:x].nil? || metrics[:y].nil?
|
10
|
-
end.to_json
|
11
|
-
series = ["var turbulenceGraphData = #{data_in_json_format};"]
|
12
|
-
|
12
|
+
def to_js
|
13
13
|
grouped_by_directory = metrics_hash.group_by do |filename, _|
|
14
14
|
directories = File.dirname(filename).split("/")
|
15
15
|
directories[0..1].join("/")
|
@@ -18,16 +18,14 @@ class Turbulence
|
|
18
18
|
directory_series = {}
|
19
19
|
grouped_by_directory.each_pair do |directory, metrics_hash|
|
20
20
|
data_in_json_format = metrics_hash.map do |filename, metrics|
|
21
|
-
{:filename => filename, :x => metrics[
|
21
|
+
{:filename => filename, :x => metrics[Turbulence::Calculators::Churn], :y => metrics[Turbulence::Calculators::Complexity]}
|
22
22
|
end.reject do |metrics|
|
23
|
-
|
23
|
+
metrics[:x].nil? || metrics[:y].nil?
|
24
24
|
end
|
25
25
|
directory_series[directory] = data_in_json_format
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
series.join("\n")
|
28
|
+
"var directorySeries = #{directory_series.to_json};"
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
data/lib/turbulence/version.rb
CHANGED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'turbulence/calculators/churn'
|
2
|
+
|
3
|
+
describe Turbulence::Calculators::Churn do
|
4
|
+
let(:calculator) { Turbulence::Calculators::Churn }
|
5
|
+
before do
|
6
|
+
calculator.stub(:git_log_command) { "" }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "::for_these_files" do
|
10
|
+
it "yields up the filename and score for each file" do
|
11
|
+
files = ["lib/corey.rb", "lib/chad.rb"]
|
12
|
+
calculator.stub(:changes_by_ruby_file) {
|
13
|
+
[
|
14
|
+
[5, "lib/corey.rb"],
|
15
|
+
[10, "lib/chad.rb"]
|
16
|
+
]
|
17
|
+
}
|
18
|
+
yielded_files = []
|
19
|
+
calculator.for_these_files(files) do |filename, score|
|
20
|
+
yielded_files << [filename, score]
|
21
|
+
end
|
22
|
+
yielded_files.should =~ [["lib/corey.rb", 5],
|
23
|
+
["lib/chad.rb",10]]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "filters the results by the passed-in files" do
|
27
|
+
files = ["lib/corey.rb"]
|
28
|
+
calculator.stub(:changes_by_ruby_file) {
|
29
|
+
[
|
30
|
+
[5, "lib/corey.rb"],
|
31
|
+
[10, "lib/chad.rb"]
|
32
|
+
]
|
33
|
+
}
|
34
|
+
yielded_files = []
|
35
|
+
calculator.for_these_files(files) do |filename, score|
|
36
|
+
yielded_files << [filename, score]
|
37
|
+
end
|
38
|
+
yielded_files.should =~ [["lib/corey.rb", 5]]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "::git_log_file_lines" do
|
43
|
+
it "returns just the file lines" do
|
44
|
+
calculator.stub(:git_log_command) do
|
45
|
+
"\n\n\n\n10\t6\tlib/turbulence.rb\n\n\n\n17\t2\tlib/eddies.rb\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
calculator.git_log_file_lines.should =~ [
|
49
|
+
"10\t6\tlib/turbulence.rb",
|
50
|
+
"17\t2\tlib/eddies.rb"
|
51
|
+
]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "::counted_line_changes_by_file_by_commit" do
|
56
|
+
before do
|
57
|
+
calculator.stub(:git_log_file_lines) {
|
58
|
+
[
|
59
|
+
"10\t6\tlib/turbulence.rb",
|
60
|
+
"17\t2\tlib/eddies.rb"
|
61
|
+
]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
it "sums up the line changes" do
|
66
|
+
calculator.counted_line_changes_by_file_by_commit.should =~ [["lib/turbulence.rb", 16], ["lib/eddies.rb", 19]]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "Full stack tests" do
|
71
|
+
context "when one ruby file is given" do
|
72
|
+
context "with two log entries for file" do
|
73
|
+
before do
|
74
|
+
calculator.stub(:git_log_command) do
|
75
|
+
"\n\n\n\n10\t6\tlib/turbulence.rb\n" +
|
76
|
+
"\n\n\n\n11\t7\tlib/turbulence.rb\n"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
it "gives the line change count for the file" do
|
80
|
+
yielded_files = []
|
81
|
+
calculator.for_these_files(["lib/turbulence.rb"]) do |filename, score|
|
82
|
+
yielded_files << [filename, score]
|
83
|
+
end
|
84
|
+
yielded_files.should =~ [["lib/turbulence.rb", 16]]
|
85
|
+
end
|
86
|
+
context "with only one log entry for file" do
|
87
|
+
before do
|
88
|
+
calculator.stub(:git_log_command) do
|
89
|
+
"\n\n\n\n10\t6\tlib/turbulence.rb\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
it "shows zero churn for the file" do
|
93
|
+
yielded_files = []
|
94
|
+
calculator.for_these_files(["lib/turbulence.rb"]) do |filename, score|
|
95
|
+
yielded_files << [filename, score]
|
96
|
+
end
|
97
|
+
yielded_files.should =~ [["lib/turbulence.rb", 0]]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'turbulence/calculators/complexity'
|
2
|
+
|
3
|
+
describe Turbulence::Calculators::Complexity do
|
4
|
+
let(:calculator) { Turbulence::Calculators::Complexity }
|
5
|
+
describe "::for_these_files" do
|
6
|
+
it "yields up the filename and score for each file" do
|
7
|
+
files = ["lib/corey.rb", "lib/chad.rb"]
|
8
|
+
calculator.stub(:score_for_file) { |filename|
|
9
|
+
filename.size
|
10
|
+
}
|
11
|
+
yielded_files = []
|
12
|
+
calculator.for_these_files(files) do |filename, score|
|
13
|
+
yielded_files << [filename, score]
|
14
|
+
end
|
15
|
+
yielded_files.should =~ [["lib/corey.rb", 12],
|
16
|
+
["lib/chad.rb",11]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Turbulence::Calculators::Complexity::Reporter do
|
22
|
+
subject { Turbulence::Calculators::Complexity::Reporter.new }
|
23
|
+
it "uses the total value from flog" do
|
24
|
+
flog_output = <<-FLOG_OUTPUT
|
25
|
+
38.7: flog total
|
26
|
+
5.5: flog/method average
|
27
|
+
|
28
|
+
9.3: Turbulence#initialize lib/turbulence.rb:9
|
29
|
+
8.7: Turbulence#churn lib/turbulence.rb:41
|
30
|
+
6.1: Turbulence#complexity lib/turbulence.rb:26
|
31
|
+
FLOG_OUTPUT
|
32
|
+
|
33
|
+
subject.stub(:string) { flog_output }
|
34
|
+
|
35
|
+
subject.score.should == 38.7
|
36
|
+
end
|
37
|
+
end
|
data/template/turbulence.html
CHANGED
@@ -22,18 +22,19 @@ $(document).ready(function() {
|
|
22
22
|
enabled: true,
|
23
23
|
text: 'Churn'
|
24
24
|
},
|
25
|
-
startOnTick:
|
26
|
-
endOnTick:
|
27
|
-
|
28
|
-
|
25
|
+
startOnTick: false,
|
26
|
+
endOnTick: false,
|
27
|
+
labels: {enabled:false},
|
28
|
+
lineWidth: 0,
|
29
|
+
tickWidth: 0
|
29
30
|
},
|
30
31
|
yAxis: {
|
31
32
|
title: {
|
32
33
|
text: 'Complexity'
|
33
34
|
},
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
startOnTick: false,
|
36
|
+
endOnTick: false,
|
37
|
+
labels: {enabled:false}
|
37
38
|
},
|
38
39
|
tooltip: {
|
39
40
|
formatter: function() {
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 4
|
9
|
+
version: 0.0.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Chad Fowler
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2011-02-
|
19
|
+
date: 2011-02-28 00:00:00 -07:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -84,8 +84,13 @@ files:
|
|
84
84
|
- Rakefile
|
85
85
|
- bin/bule
|
86
86
|
- lib/turbulence.rb
|
87
|
+
- lib/turbulence/calculators/churn.rb
|
88
|
+
- lib/turbulence/calculators/complexity.rb
|
89
|
+
- lib/turbulence/command_line_interface.rb
|
87
90
|
- lib/turbulence/scatter_plot_generator.rb
|
88
91
|
- lib/turbulence/version.rb
|
92
|
+
- spec/turbulence/calculators/churn_spec.rb
|
93
|
+
- spec/turbulence/calculators/complexity_spec.rb
|
89
94
|
- template/highchart_template.js.erb
|
90
95
|
- template/highcharts.js
|
91
96
|
- template/jquery.min.js
|
@@ -123,5 +128,6 @@ rubygems_version: 1.3.7
|
|
123
128
|
signing_key:
|
124
129
|
specification_version: 3
|
125
130
|
summary: Automates churn + flog scoring on a git repo for a Ruby project
|
126
|
-
test_files:
|
127
|
-
|
131
|
+
test_files:
|
132
|
+
- spec/turbulence/calculators/churn_spec.rb
|
133
|
+
- spec/turbulence/calculators/complexity_spec.rb
|