turbulence 1.2.3 → 1.3.0
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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +51 -0
- data/CHANGELOG.md +208 -0
- data/Gemfile.lock +47 -24
- data/LICENSE.txt +7 -0
- data/README.md +100 -31
- data/bin/bule +1 -1
- data/docs/scatter-plot.png +0 -0
- data/docs/treemap.png +0 -0
- data/lib/turbulence/calculators/churn.rb +46 -34
- data/lib/turbulence/calculators/complexity.rb +19 -27
- data/lib/turbulence/checks_environment.rb +2 -1
- data/lib/turbulence/cli_parser.rb +53 -0
- data/lib/turbulence/command_line_interface.rb +30 -48
- data/lib/turbulence/configuration.rb +30 -0
- data/lib/turbulence/generators/scatterplot.rb +2 -2
- data/lib/turbulence/generators/treemap.rb +2 -2
- data/lib/turbulence/scm/git.rb +1 -1
- data/lib/turbulence/version.rb +1 -1
- data/lib/turbulence.rb +39 -20
- data/spec/turbulence/calculators/churn_spec.rb +53 -63
- data/spec/turbulence/calculators/complexity_spec.rb +5 -6
- data/spec/turbulence/cli_parser_spec.rb +50 -0
- data/spec/turbulence/command_line_interface_spec.rb +34 -19
- data/spec/turbulence/configuration_spec.rb +19 -0
- data/spec/turbulence/generators/scatter_plot_spec.rb +49 -44
- data/spec/turbulence/generators/treemap_spec.rb +7 -7
- data/spec/turbulence/scm/git_spec.rb +7 -6
- data/spec/turbulence/scm/perforce_spec.rb +44 -40
- data/spec/turbulence/turbulence_spec.rb +15 -7
- data/turbulence.gemspec +13 -9
- metadata +65 -32
- data/.travis.yml +0 -6
- data/spec/turbulence/checks_environoment_spec.rb +0 -11
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
require 'turbulence'
|
|
3
|
+
|
|
4
|
+
describe Turbulence::CommandLineInterface::ConfigParser do
|
|
5
|
+
let(:config) { Turbulence::Configuration.new }
|
|
6
|
+
|
|
7
|
+
def parse(argv)
|
|
8
|
+
described_class.parse_argv_into_config(argv, config)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "sets directory" do
|
|
12
|
+
parse %w( path/to/compute )
|
|
13
|
+
expect(config.directory).to eq 'path/to/compute'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "sets SCM name to 'Perforce'" do
|
|
17
|
+
parse %w( --scm p4 )
|
|
18
|
+
expect(config.scm_name).to eq 'Perforce'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "sets commit range" do
|
|
22
|
+
parse %w( --churn-range f3e1d7a6..830b9d3d9f )
|
|
23
|
+
expect(config.commit_range).to eq 'f3e1d7a6..830b9d3d9f'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "sets compute mean" do
|
|
27
|
+
parse %w( --churn-mean )
|
|
28
|
+
expect(config.compute_mean).to be true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "sets the exclusion pattern" do
|
|
32
|
+
parse %w( --exclude turbulence )
|
|
33
|
+
expect(config.exclusion_pattern).to eq 'turbulence'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "sets the graph type" do
|
|
37
|
+
parse %w( --treemap )
|
|
38
|
+
expect(config.graph_type).to eq 'treemap'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "sets no_open" do
|
|
42
|
+
parse %w( --no-open )
|
|
43
|
+
expect(config.no_open).to be true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "sets output_dir" do
|
|
47
|
+
parse %w( --output spec/reports/turbulence )
|
|
48
|
+
expect(config.output_dir).to eq 'spec/reports/turbulence'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -3,48 +3,63 @@ require 'turbulence'
|
|
|
3
3
|
|
|
4
4
|
describe Turbulence::CommandLineInterface do
|
|
5
5
|
let(:cli) { Turbulence::CommandLineInterface.new(%w(.), :output => nil) }
|
|
6
|
+
|
|
6
7
|
describe "::TEMPLATE_FILES" do
|
|
7
8
|
Turbulence::CommandLineInterface::TEMPLATE_FILES.each do |template_file|
|
|
8
|
-
File.
|
|
9
|
+
it "has #{File.basename(template_file)} in the template path" do
|
|
10
|
+
expect(File.dirname(template_file)).to eq Turbulence::CommandLineInterface::TURBULENCE_TEMPLATE_PATH
|
|
11
|
+
end
|
|
9
12
|
end
|
|
10
13
|
end
|
|
14
|
+
|
|
11
15
|
describe "#generate_bundle" do
|
|
12
16
|
before do
|
|
13
17
|
FileUtils.remove_dir("turbulence", true)
|
|
14
18
|
end
|
|
15
19
|
it "bundles the files" do
|
|
16
20
|
cli.generate_bundle
|
|
17
|
-
Dir.glob('turbulence/*').sort.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
expect(Dir.glob('turbulence/*').sort).to eq(["turbulence/cc.js",
|
|
22
|
+
"turbulence/highcharts.js",
|
|
23
|
+
"turbulence/jquery.min.js",
|
|
24
|
+
"turbulence/treemap.html",
|
|
25
|
+
"turbulence/turbulence.html"])
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
it "passes along exclusion pattern" do
|
|
25
29
|
cli = Turbulence::CommandLineInterface.new(%w(--exclude turbulence), :output => nil)
|
|
26
30
|
cli.generate_bundle
|
|
27
31
|
lines = File.new('turbulence/cc.js').readlines
|
|
28
|
-
lines.any? { |l| l =~ /turbulence\.rb/ }.
|
|
32
|
+
expect(lines.any? { |l| l =~ /turbulence\.rb/ }).to be false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "outputs to custom directory when --output is specified" do
|
|
36
|
+
custom_dir = 'tmp/custom_output'
|
|
37
|
+
FileUtils.remove_dir(custom_dir, true)
|
|
38
|
+
cli = Turbulence::CommandLineInterface.new(['--output', custom_dir], :output => nil)
|
|
39
|
+
cli.generate_bundle
|
|
40
|
+
expect(Dir.glob("#{custom_dir}/*").sort).to eq(["#{custom_dir}/cc.js",
|
|
41
|
+
"#{custom_dir}/highcharts.js",
|
|
42
|
+
"#{custom_dir}/jquery.min.js",
|
|
43
|
+
"#{custom_dir}/treemap.html",
|
|
44
|
+
"#{custom_dir}/turbulence.html"])
|
|
45
|
+
FileUtils.remove_dir(custom_dir, true)
|
|
29
46
|
end
|
|
30
47
|
end
|
|
31
|
-
describe "command line options" do
|
|
32
|
-
let(:cli_churn_range) { Turbulence::CommandLineInterface.new(%w(--churn-range f3e1d7a6..830b9d3d9f path/to/compute)) }
|
|
33
|
-
let(:cli_churn_mean) { Turbulence::CommandLineInterface.new(%w(--churn-mean .)) }
|
|
34
|
-
let(:cli_exclusion_pattern) { Turbulence::CommandLineInterface.new(%w(--exclude turbulence)) }
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
describe "#output_path" do
|
|
50
|
+
before do
|
|
51
|
+
# Reset the singleton config between tests
|
|
52
|
+
Turbulence.instance_variable_set(:@config, nil)
|
|
39
53
|
end
|
|
40
54
|
|
|
41
|
-
it "
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
it "defaults to ./turbulence when output_dir is not set" do
|
|
56
|
+
cli = Turbulence::CommandLineInterface.new(%w(.), :output => nil)
|
|
57
|
+
expect(cli.output_path).to eq(File.join(Dir.pwd, "turbulence"))
|
|
44
58
|
end
|
|
45
59
|
|
|
46
|
-
it "
|
|
47
|
-
|
|
60
|
+
it "returns the custom output_dir when set" do
|
|
61
|
+
cli = Turbulence::CommandLineInterface.new(%w(--output custom/path), :output => nil)
|
|
62
|
+
expect(cli.output_path).to eq('custom/path')
|
|
48
63
|
end
|
|
49
64
|
end
|
|
50
65
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
require 'rspec/its'
|
|
3
|
+
require 'turbulence'
|
|
4
|
+
|
|
5
|
+
describe Turbulence::Configuration do
|
|
6
|
+
describe "defaults" do
|
|
7
|
+
its(:output) { should eq(STDOUT) }
|
|
8
|
+
its(:directory) { should eq(Dir.pwd) }
|
|
9
|
+
its(:graph_type) { should eq('turbulence') }
|
|
10
|
+
its(:scm_name) { should eq('Git') }
|
|
11
|
+
its(:scm) { should eq(Turbulence::Scm::Git) }
|
|
12
|
+
its(:no_open) { should eq(false) }
|
|
13
|
+
its(:output_dir) { should be_nil }
|
|
14
|
+
its(:commit_range) { should be_nil }
|
|
15
|
+
its(:compute_mean) { should be_nil }
|
|
16
|
+
its(:exclusion_pattern) { should be_nil }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
@@ -2,93 +2,98 @@ require 'turbulence'
|
|
|
2
2
|
|
|
3
3
|
describe Turbulence::Generators::ScatterPlot do
|
|
4
4
|
context "with both Metrics" do
|
|
5
|
-
it "generates JavaScript" do
|
|
5
|
+
it "generates JavaScript", :aggregate_failures do
|
|
6
6
|
generator = Turbulence::Generators::ScatterPlot.new(
|
|
7
|
-
"foo.rb" => {
|
|
8
|
-
|
|
7
|
+
"foo.rb" => { :churn => 1,
|
|
8
|
+
:complexity => 2 }
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
-
generator.to_js.
|
|
12
|
-
generator.to_js.
|
|
13
|
-
generator.to_js.
|
|
14
|
-
generator.to_js.
|
|
11
|
+
expect(generator.to_js).to match(/var directorySeries/)
|
|
12
|
+
expect(generator.to_js).to match(/\"filename\"\:\"foo.rb\"/)
|
|
13
|
+
expect(generator.to_js).to match(/\"x\":1/)
|
|
14
|
+
expect(generator.to_js).to match(/\"y\":2/)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
context "with a missing Metric" do
|
|
19
19
|
it "generates JavaScript" do
|
|
20
20
|
generator = Turbulence::Generators::ScatterPlot.new(
|
|
21
|
-
"foo.rb" => {
|
|
21
|
+
"foo.rb" => { :churn => 1 }
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
generator.to_js.
|
|
24
|
+
expect(generator.to_js).to eq 'var directorySeries = {};'
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
describe "#clean_metrics_from_missing_data" do
|
|
29
|
-
let(:spg) {Turbulence::Generators::ScatterPlot.new({})}
|
|
29
|
+
let(:spg) { Turbulence::Generators::ScatterPlot.new({}) }
|
|
30
30
|
|
|
31
31
|
it "removes entries with missing churn" do
|
|
32
|
-
spg.
|
|
33
|
-
|
|
34
|
-
spg.clean_metrics_from_missing_data.should == {}
|
|
32
|
+
allow(spg).to receive(:metrics_hash).and_return("foo.rb" => { :complexity => 88.3 })
|
|
33
|
+
expect(spg.clean_metrics_from_missing_data).to eq({})
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
it "removes entries with missing complexity" do
|
|
38
|
-
spg.
|
|
39
|
-
|
|
40
|
-
spg.clean_metrics_from_missing_data.should == {}
|
|
37
|
+
allow(spg).to receive(:metrics_hash).and_return("foo.rb" => { :churn => 1 })
|
|
38
|
+
expect(spg.clean_metrics_from_missing_data).to eq({})
|
|
41
39
|
end
|
|
42
40
|
|
|
43
41
|
it "keeps entries with churn and complexity present" do
|
|
44
|
-
spg.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
allow(spg).to receive(:metrics_hash).and_return("foo.rb" => {
|
|
43
|
+
:churn => 1,
|
|
44
|
+
:complexity => 88.3,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
expect(spg.clean_metrics_from_missing_data).not_to eq({})
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
describe "#grouped_by_directory" do
|
|
52
|
-
let(:spg) {
|
|
53
|
-
Turbulence::
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
let(:spg) {
|
|
53
|
+
Turbulence::Generators::ScatterPlot.new(
|
|
54
|
+
"lib/foo/foo.rb" => { :churn => 1 },
|
|
55
|
+
"lib/bar.rb" => { :churn => 2 }
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
it "uses \".\" to denote flat hierarchy" do
|
|
60
|
+
allow(spg).to receive(:metrics_hash).and_return("foo.rb" => { :churn => 1 })
|
|
61
|
+
expect(spg.grouped_by_directory).to eq({ "." => [["foo.rb", { :churn => 1 }]] })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "takes full path into account" do
|
|
65
|
+
expect(spg.grouped_by_directory).to eq({
|
|
66
|
+
"lib/foo" => [["lib/foo/foo.rb", { :churn => 1 }]],
|
|
67
|
+
"lib" => [["lib/bar.rb", { :churn => 2 }]]
|
|
68
|
+
})
|
|
69
|
+
end
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
describe "#file_metrics_for_directory" do
|
|
71
|
-
let(:spg) {Turbulence::Generators::ScatterPlot.new({})}
|
|
73
|
+
let(:spg) { Turbulence::Generators::ScatterPlot.new({}) }
|
|
74
|
+
|
|
72
75
|
it "assigns :filename, :x, :y" do
|
|
73
|
-
spg.file_metrics_for_directory("lib/foo/foo.rb" => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
result = spg.file_metrics_for_directory("lib/foo/foo.rb" => {
|
|
77
|
+
:churn => 1,
|
|
78
|
+
:complexity => 88.2,
|
|
79
|
+
})
|
|
80
|
+
expect(result).to eq [{ :filename => "lib/foo/foo.rb", :x => 1, :y => 88.2 }]
|
|
77
81
|
end
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
describe Turbulence::FileNameMangler do
|
|
81
85
|
subject { Turbulence::FileNameMangler.new }
|
|
86
|
+
|
|
82
87
|
it "anonymizes a string" do
|
|
83
|
-
subject.mangle_name("chad").
|
|
88
|
+
expect(subject.mangle_name("chad")).not_to eq "chad"
|
|
84
89
|
end
|
|
85
90
|
|
|
86
91
|
it "maintains standard directory names" do
|
|
87
|
-
subject.mangle_name("/app/controllers/chad.rb").
|
|
92
|
+
expect(subject.mangle_name("/app/controllers/chad.rb")).to match(%r{/app/controllers/1.rb})
|
|
88
93
|
end
|
|
89
94
|
|
|
90
95
|
it "honors leading path separators" do
|
|
91
|
-
subject.mangle_name("/a/b/c.rb").
|
|
96
|
+
expect(subject.mangle_name("/a/b/c.rb")).to eq "/1/2/3.rb"
|
|
92
97
|
end
|
|
93
98
|
end
|
|
94
99
|
end
|
|
@@ -2,24 +2,24 @@ require 'turbulence'
|
|
|
2
2
|
|
|
3
3
|
describe Turbulence::Generators::TreeMap do
|
|
4
4
|
context "with both Metrics" do
|
|
5
|
-
it "generates JavaScript" do
|
|
5
|
+
it "generates JavaScript", :aggregate_failures do
|
|
6
6
|
generator = Turbulence::Generators::TreeMap.new(
|
|
7
|
-
"foo.rb" => {
|
|
8
|
-
|
|
7
|
+
"foo.rb" => { :churn => 1,
|
|
8
|
+
:complexity => 2 }
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
-
generator.build_js.
|
|
12
|
-
generator.build_js.
|
|
11
|
+
expect(generator.build_js).to match(/var treemap_data/)
|
|
12
|
+
expect(generator.build_js).to match(/\'foo.rb\'/)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
context "with a missing Metric" do
|
|
17
17
|
it "generates JavaScript" do
|
|
18
18
|
generator = Turbulence::Generators::TreeMap.new(
|
|
19
|
-
"foo.rb" => {
|
|
19
|
+
"foo.rb" => { :churn => 1 }
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
generator.build_js.
|
|
22
|
+
expect(generator.build_js).to eq "var treemap_data = [['File', 'Parent', 'Churn (size)', 'Complexity (color)'],\n['Root', null, 0, 0],\n];"
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -5,25 +5,26 @@ require 'fileutils'
|
|
|
5
5
|
describe Turbulence::Scm::Git do
|
|
6
6
|
describe "::is_repo?" do
|
|
7
7
|
before do
|
|
8
|
-
|
|
8
|
+
# Create temp dir in system temp location (outside any git repo)
|
|
9
|
+
@tmp = Dir.mktmpdir('turbulence-test')
|
|
9
10
|
end
|
|
10
11
|
after do
|
|
11
|
-
FileUtils.
|
|
12
|
+
FileUtils.remove_entry(@tmp)
|
|
12
13
|
end
|
|
13
14
|
it "returns true for the working directory" do
|
|
14
|
-
Turbulence::Scm::Git.is_repo?(".").
|
|
15
|
+
expect(Turbulence::Scm::Git.is_repo?(".")).to eq true
|
|
15
16
|
end
|
|
16
17
|
it "return false for a newly created tmp directory" do
|
|
17
|
-
Turbulence::Scm::Git.is_repo?(@tmp).
|
|
18
|
+
expect(Turbulence::Scm::Git.is_repo?(@tmp)).to eq false
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
describe "::log_command" do
|
|
22
23
|
it "takes an optional argument specify to the range" do
|
|
23
|
-
expect{Turbulence::Scm::Git.log_command("d551e63f79a90430e560ea871f4e1e39e6e739bd HEAD")}.to_not raise_error
|
|
24
|
+
expect { Turbulence::Scm::Git.log_command("d551e63f79a90430e560ea871f4e1e39e6e739bd HEAD") }.to_not raise_error
|
|
24
25
|
end
|
|
25
26
|
it "lists insertions/deletions per file and change" do
|
|
26
|
-
Turbulence::Scm::Git.log_command.
|
|
27
|
+
expect(Turbulence::Scm::Git.log_command).to match(/\d+\t\d+\t[A-z.]*/)
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
end
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
require 'turbulence/scm/perforce'
|
|
2
|
-
require 'rspec/mocks'
|
|
3
2
|
|
|
4
3
|
describe Turbulence::Scm::Perforce do
|
|
5
|
-
let
|
|
4
|
+
let(:p4_scm) { Turbulence::Scm::Perforce }
|
|
6
5
|
|
|
7
6
|
before do
|
|
8
|
-
p4_scm.
|
|
7
|
+
allow(p4_scm).to receive(:p4_list_changes).and_return(
|
|
9
8
|
"Change 62660 on 2005/11/28 by x@client 'CHANGED: adapted to DESCODE '
|
|
10
9
|
Change 45616 on 2005/07/12 by x@client 'ADDED: trigger that builds and '
|
|
11
10
|
Change 45615 on 2005/07/12 by x@client 'ADDED: for testing purposes '
|
|
@@ -13,9 +12,9 @@ Change 45614 on 2005/07/12 by x@client 'COSMETIC: updated header '
|
|
|
13
12
|
Change 11250 on 2004/09/17 by x@client 'CHANGED: trigger now also allow'
|
|
14
13
|
Change 9250 on 2004/08/20 by x@client 'BUGFIX: bug#1583 (People can so'
|
|
15
14
|
Change 5560 on 2004/04/26 by x@client 'ADDED: The \"BRANCHED\" tag.'"
|
|
16
|
-
|
|
15
|
+
)
|
|
17
16
|
|
|
18
|
-
p4_scm.
|
|
17
|
+
allow(p4_scm).to receive(:p4_describe_change).with("5560").and_return(
|
|
19
18
|
"Change 5560 by x@client on 2004/04/26 17:25:03
|
|
20
19
|
|
|
21
20
|
ADDED: The \"BRANCHED\" tag.
|
|
@@ -38,105 +37,111 @@ changed 1 chunks 3 / 3 lines
|
|
|
38
37
|
add 0 chunks 0 lines
|
|
39
38
|
deleted 0 chunks 0 lines
|
|
40
39
|
changed 1 chunks 3 / 1 lines"
|
|
41
|
-
|
|
40
|
+
)
|
|
42
41
|
end
|
|
43
42
|
|
|
44
43
|
describe "::is_repo?" do
|
|
45
44
|
before :each do
|
|
46
|
-
ENV.
|
|
47
|
-
ENV.
|
|
45
|
+
allow(ENV).to receive(:[]).with("P4CLIENT").and_return(nil)
|
|
46
|
+
allow(ENV).to receive(:[]).with("PATH").and_return("")
|
|
48
47
|
end
|
|
49
48
|
|
|
50
49
|
it "returns true if P4CLIENT is set " do
|
|
51
|
-
ENV.
|
|
50
|
+
allow(ENV).to receive(:[]).with("P4CLIENT").and_return("c-foo.bar")
|
|
52
51
|
|
|
53
|
-
Turbulence::Scm::Perforce.is_repo?(".").
|
|
52
|
+
expect(Turbulence::Scm::Perforce.is_repo?(".")).to be true
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
it "returns false if P4CLIENT is empty"
|
|
57
|
-
Turbulence::Scm::Perforce.is_repo?(".").
|
|
55
|
+
it "returns false if P4CLIENT is empty" do
|
|
56
|
+
expect(Turbulence::Scm::Perforce.is_repo?(".")).to be false
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
it "returns false if p4 is not available" do
|
|
61
|
-
Turbulence::Scm::Perforce.is_repo?(".").
|
|
60
|
+
expect(Turbulence::Scm::Perforce.is_repo?(".")).to be false
|
|
62
61
|
end
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
describe "::log_command" do
|
|
66
65
|
before do
|
|
67
|
-
p4_scm.
|
|
66
|
+
allow(p4_scm).to receive(:depot_to_local)
|
|
67
|
+
.with("//admin/scripts/triggers/enforce-submit-comment.py")
|
|
68
68
|
.and_return("triggers/enforce-submit-comments.py")
|
|
69
|
-
p4_scm.
|
|
69
|
+
allow(p4_scm).to receive(:depot_to_local)
|
|
70
|
+
.with("//admin/scripts/triggers/check-consistency.py")
|
|
70
71
|
.and_return("triggers/check-consistency.py")
|
|
71
|
-
p4_scm.
|
|
72
|
+
allow(p4_scm).to receive(:p4_list_changes).and_return(
|
|
72
73
|
"Change 5560 on 2004/04/26 by x@client 'ADDED: The \"BRANCHED\" tag.'"
|
|
73
|
-
|
|
74
|
+
)
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
it "takes an optional argument to specify the range" do
|
|
77
|
-
expect{Turbulence::Scm::Perforce.log_command("@1,2")}.to_not raise_error
|
|
78
|
+
expect { Turbulence::Scm::Perforce.log_command("@1,2") }.to_not raise_error
|
|
78
79
|
end
|
|
79
80
|
|
|
80
81
|
it "lists insertions/deletions per file and change" do
|
|
81
|
-
Turbulence::Scm::Perforce.log_command().
|
|
82
|
+
expect(Turbulence::Scm::Perforce.log_command()).to match(/\d+\t\d+\t[A-z.]*/)
|
|
82
83
|
end
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
describe "::changes" do
|
|
86
87
|
it "lists changenumbers from parsing 'p4 changes' output" do
|
|
87
|
-
p4_scm.changes.
|
|
88
|
+
expect(p4_scm.changes).to match_array(%w[62660 45616 45615 45614 11250 9250 5560])
|
|
88
89
|
end
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
describe "::files_per_change" do
|
|
92
93
|
before do
|
|
93
|
-
p4_scm.
|
|
94
|
+
allow(p4_scm).to receive(:depot_to_local)
|
|
95
|
+
.with("//admin/scripts/triggers/enforce-submit-comment.py")
|
|
94
96
|
.and_return("triggers/enforce-submit-comments.py")
|
|
95
|
-
p4_scm.
|
|
97
|
+
allow(p4_scm).to receive(:depot_to_local)
|
|
98
|
+
.with("//admin/scripts/triggers/check-consistency.py")
|
|
96
99
|
.and_return("triggers/check-consistency.py")
|
|
97
100
|
end
|
|
98
101
|
|
|
99
102
|
it "lists files with churn" do
|
|
100
|
-
p4_scm.files_per_change("5560").
|
|
101
|
-
[
|
|
103
|
+
expect(p4_scm.files_per_change("5560")).to match_array([
|
|
104
|
+
[4, "triggers/enforce-submit-comments.py"],
|
|
105
|
+
[1, "triggers/check-consistency.py"]
|
|
106
|
+
])
|
|
102
107
|
end
|
|
103
108
|
end
|
|
104
109
|
|
|
105
110
|
describe "::transform_for_output" do
|
|
106
111
|
it "adds a 0 for deletions" do
|
|
107
|
-
p4_scm.transform_for_output([1,"triggers/check-consistency.py"]).
|
|
112
|
+
expect(p4_scm.transform_for_output([1, "triggers/check-consistency.py"])).to eq "1\t0\ttriggers/check-consistency.py\n"
|
|
108
113
|
end
|
|
109
114
|
end
|
|
110
115
|
|
|
111
116
|
describe "::depot_to_local" do
|
|
112
117
|
describe "on windows" do
|
|
113
118
|
before do
|
|
114
|
-
p4_scm.
|
|
115
|
-
|
|
119
|
+
allow(p4_scm).to receive(:extract_clientfile_from_fstat_of)
|
|
120
|
+
.and_return("D:/Perforce/admin/scripts/triggers/enforce-no-head-change.py")
|
|
121
|
+
allow(FileUtils).to receive(:pwd).and_return("D:/Perforce")
|
|
116
122
|
end
|
|
117
123
|
|
|
118
124
|
it "converts depot-style paths to local paths using forward slashes" do
|
|
119
|
-
p4_scm.depot_to_local("//admin/scripts/triggers/enforce-no-head-change.py").
|
|
120
|
-
== "admin/scripts/triggers/enforce-no-head-change.py"
|
|
125
|
+
expect(p4_scm.depot_to_local("//admin/scripts/triggers/enforce-no-head-change.py")).to eq "admin/scripts/triggers/enforce-no-head-change.py"
|
|
121
126
|
end
|
|
122
127
|
end
|
|
123
128
|
|
|
124
129
|
describe "on unix" do
|
|
125
130
|
before do
|
|
126
|
-
p4_scm.
|
|
127
|
-
|
|
131
|
+
allow(p4_scm).to receive(:extract_clientfile_from_fstat_of)
|
|
132
|
+
.and_return("/home/jhwist/admin/scripts/triggers/enforce-no-head-change.py")
|
|
133
|
+
allow(FileUtils).to receive(:pwd).and_return("/home/jhwist")
|
|
128
134
|
end
|
|
129
135
|
|
|
130
136
|
it "converts depot-style paths to local paths using forward slashes" do
|
|
131
|
-
p4_scm.depot_to_local("//admin/scripts/triggers/enforce-no-head-change.py").
|
|
132
|
-
== "admin/scripts/triggers/enforce-no-head-change.py"
|
|
137
|
+
expect(p4_scm.depot_to_local("//admin/scripts/triggers/enforce-no-head-change.py")).to eq "admin/scripts/triggers/enforce-no-head-change.py"
|
|
133
138
|
end
|
|
134
139
|
end
|
|
135
140
|
end
|
|
136
141
|
|
|
137
142
|
describe "::extract_clientfile_from_fstat_of" do
|
|
138
143
|
before do
|
|
139
|
-
p4_scm.
|
|
144
|
+
allow(p4_scm).to receive(:p4_fstat).and_return(
|
|
140
145
|
"... depotFile //admin/scripts/triggers/enforce-no-head-change.py
|
|
141
146
|
... clientFile /home/jhwist/admin/scripts/triggers/enforce-no-head-change.py
|
|
142
147
|
... isMapped
|
|
@@ -147,24 +152,23 @@ changed 1 chunks 3 / 1 lines"
|
|
|
147
152
|
... headChange 211211
|
|
148
153
|
... headModTime 1214555028
|
|
149
154
|
... haveRev 5"
|
|
150
|
-
|
|
155
|
+
)
|
|
151
156
|
end
|
|
152
157
|
|
|
153
|
-
it "uses clientFile field"
|
|
154
|
-
p4_scm.extract_clientfile_from_fstat_of("//admin/scripts/triggers/enforce-no-head-change.py").
|
|
155
|
-
"/home/jhwist/admin/scripts/triggers/enforce-no-head-change.py"
|
|
158
|
+
it "uses clientFile field" do
|
|
159
|
+
expect(p4_scm.extract_clientfile_from_fstat_of("//admin/scripts/triggers/enforce-no-head-change.py")).to eq "/home/jhwist/admin/scripts/triggers/enforce-no-head-change.py"
|
|
156
160
|
end
|
|
157
161
|
end
|
|
158
162
|
|
|
159
163
|
describe "::sum_of_changes" do
|
|
160
164
|
it "sums up changes" do
|
|
161
165
|
output = "add 1 chunks 1 lines\ndeleted 0 chunks 0 lines\nchanged 1 chunks 3 / 3 lines"
|
|
162
|
-
p4_scm.sum_of_changes(output).
|
|
166
|
+
expect(p4_scm.sum_of_changes(output)).to eq 4
|
|
163
167
|
end
|
|
164
168
|
|
|
165
169
|
it "ignores junk" do
|
|
166
170
|
output = "add nothing, change nothing"
|
|
167
|
-
p4_scm.sum_of_changes(output).
|
|
171
|
+
expect(p4_scm.sum_of_changes(output)).to eq 0
|
|
168
172
|
end
|
|
169
173
|
end
|
|
170
174
|
end
|
|
@@ -2,14 +2,22 @@ require 'rspec'
|
|
|
2
2
|
require 'turbulence'
|
|
3
3
|
|
|
4
4
|
describe Turbulence do
|
|
5
|
+
subject(:turb) { Turbulence.new(config) }
|
|
6
|
+
|
|
7
|
+
let(:config) {
|
|
8
|
+
Turbulence::Configuration.new.tap do |config|
|
|
9
|
+
config.directory = '.'
|
|
10
|
+
config.exclusion_pattern = nil
|
|
11
|
+
config.output = nil
|
|
12
|
+
end
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
it "finds files of interest" do
|
|
6
|
-
turb
|
|
7
|
-
turb.exclusion_pattern.should be_nil
|
|
8
|
-
turb.files_of_interest.should include "lib/turbulence.rb"
|
|
16
|
+
expect(turb.files_of_interest).to include "lib/turbulence.rb"
|
|
9
17
|
end
|
|
10
|
-
|
|
11
|
-
it "filters out
|
|
12
|
-
|
|
13
|
-
turb.files_of_interest.
|
|
18
|
+
|
|
19
|
+
it "filters out excluded files" do
|
|
20
|
+
config.exclusion_pattern = 'turbulence'
|
|
21
|
+
expect(turb.files_of_interest).not_to include "lib/turbulence.rb"
|
|
14
22
|
end
|
|
15
23
|
end
|
data/turbulence.gemspec
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
$:.push File.expand_path("../lib", __FILE__)
|
|
3
4
|
require "turbulence/version"
|
|
4
5
|
|
|
@@ -8,21 +9,24 @@ Gem::Specification.new do |s|
|
|
|
8
9
|
s.platform = Gem::Platform::RUBY
|
|
9
10
|
s.authors = ["Chad Fowler", "Michael Feathers", "Corey Haines"]
|
|
10
11
|
s.email = ["chad@chadfowler.com", "mfeathers@obtiva.com", "coreyhaines@gmail.com"]
|
|
11
|
-
s.homepage = "
|
|
12
|
-
s.
|
|
13
|
-
|
|
12
|
+
s.homepage = "https://github.com/chad/turbulence"
|
|
13
|
+
s.license = "MIT"
|
|
14
|
+
|
|
15
|
+
s.add_dependency "flog", ">= 4.1"
|
|
16
|
+
s.add_dependency "json"
|
|
14
17
|
s.add_dependency "launchy", ">= 2.0.0"
|
|
15
|
-
s.
|
|
18
|
+
s.add_dependency "racc" # Required by flog's ruby_parser, removed from stdlib in Ruby 3.3+
|
|
19
|
+
|
|
20
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
|
21
|
+
s.add_development_dependency 'rspec-its'
|
|
16
22
|
s.add_development_dependency 'rake'
|
|
17
23
|
|
|
18
24
|
s.summary = %q{Automates churn + flog scoring on a git repo for a Ruby project}
|
|
19
|
-
s.description = %q{Based on
|
|
20
|
-
|
|
21
|
-
s.rubyforge_project = "turbulence"
|
|
25
|
+
s.description = %q{Automates churn + flog scoring on a git repo for a Ruby project. Based on the article https://www.stickyminds.com/article/getting-empirical-about-refactoring}
|
|
22
26
|
|
|
23
27
|
s.files = `git ls-files`.split("\n")
|
|
24
28
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
25
29
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
26
30
|
s.require_paths = ["lib"]
|
|
27
|
-
s.required_ruby_version = '>=
|
|
31
|
+
s.required_ruby_version = '>= 3.0'
|
|
28
32
|
end
|