rails_stats 2.0.1 → 2.2.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +23 -15
  3. data/CHANGELOG.md +18 -3
  4. data/Gemfile +2 -2
  5. data/README.md +224 -232
  6. data/bin/rails-stats +27 -0
  7. data/lib/rails_stats/all.rb +8 -0
  8. data/lib/rails_stats/app_statistics.rb +1 -1
  9. data/lib/rails_stats/code_statistics_calculator.rb +18 -0
  10. data/lib/rails_stats/console_formatter.rb +60 -12
  11. data/lib/rails_stats/cucumber_statistics.rb +1 -1
  12. data/lib/rails_stats/gem_statistics.rb +1 -1
  13. data/lib/rails_stats/json_formatter.rb +59 -6
  14. data/lib/rails_stats/pack_finder.rb +67 -0
  15. data/lib/rails_stats/root_statistics.rb +1 -1
  16. data/lib/rails_stats/spec_statistics.rb +1 -1
  17. data/lib/rails_stats/stats_calculator.rb +135 -5
  18. data/lib/rails_stats/stats_formatter.rb +1 -0
  19. data/lib/rails_stats/tasks.rb +4 -1
  20. data/lib/rails_stats/test_statistics.rb +1 -1
  21. data/lib/rails_stats/util.rb +10 -2
  22. data/lib/rails_stats/version.rb +1 -1
  23. data/rails_stats.gemspec +4 -3
  24. data/test/dummy/app/javascript/dummy.jsx +1 -0
  25. data/test/dummy/app/javascript/dummy.tsx +0 -0
  26. data/test/dummy/app/javascript/index.ts +0 -0
  27. data/test/dummy/app/models/comment.rb +3 -0
  28. data/test/dummy/app/models/pet.rb +2 -0
  29. data/test/dummy/app/models/user.rb +2 -0
  30. data/test/dummy/db/schema.rb +10 -0
  31. data/test/fixtures/console-output.txt +8 -24
  32. data/test/fixtures/packwerk_app/app/controllers/core_controller.rb +4 -0
  33. data/test/fixtures/packwerk_app/app/models/core_model.rb +5 -0
  34. data/test/fixtures/packwerk_app/components/billing/app/models/billing_model.rb +4 -0
  35. data/test/fixtures/packwerk_app/components/billing/lib/billing.rb +2 -0
  36. data/test/fixtures/packwerk_app/lib/core_lib.rb +3 -0
  37. data/test/fixtures/packwerk_app/package.yml +1 -0
  38. data/test/fixtures/packwerk_app/packs/pack1/app/controllers/pack1_controller.rb +4 -0
  39. data/test/fixtures/packwerk_app/packs/pack1/app/models/pack1_model.rb +6 -0
  40. data/test/fixtures/packwerk_app/packs/pack1/lib/pack1_lib.rb +2 -0
  41. data/test/fixtures/packwerk_app/packs/pack1/package.yml +3 -0
  42. data/test/fixtures/packwerk_app/packs/pack1/spec/models/pack1_model_spec.rb +4 -0
  43. data/test/fixtures/packwerk_app/packs/pack2/app/models/pack2_model.rb +4 -0
  44. data/test/fixtures/packwerk_app/packs/pack2/test/models/pack2_model_test.rb +4 -0
  45. data/test/lib/rails_stats/code_statistics_test.rb +5 -5
  46. data/test/lib/rails_stats/console_formatter_test.rb +48 -0
  47. data/test/lib/rails_stats/json_formatter_test.rb +70 -127
  48. data/test/lib/rails_stats/pack_finder_test.rb +29 -0
  49. data/test/lib/rails_stats/stats_calculator_test.rb +64 -0
  50. data/test/lib/rails_stats/util_test.rb +49 -0
  51. metadata +56 -3
@@ -1,22 +1,3 @@
1
- +-----------------------|------------|----------------+
2
- | Name | Total Deps | 1st Level Deps |
3
- +-----------------------|------------|----------------+
4
- | simplecov-console | 7 | 3 |
5
- | codecov | 5 | 2 |
6
- | rails_stats | 4 | 2 |
7
- | simplecov | 3 | 3 |
8
- | minitest-around | 1 | 1 |
9
- | bundler | 0 | 0 |
10
- | byebug | 0 | 0 |
11
- | minitest | 0 | 0 |
12
- | minitest-spec-context | 0 | 0 |
13
- +-----------------------|------------|----------------+
14
-
15
- Declared Gems 9
16
- Total Gems 18
17
- Unpinned Versions 8
18
- Github Refs 0
19
-
20
1
  +----------------------+---------+---------+---------+---------+---------+-----+-------+
21
2
  | Name | Files | Lines | LOC | Classes | Methods | M/C | LOC/M |
22
3
  +----------------------+---------+---------+---------+---------+---------+-----+-------+
@@ -24,17 +5,20 @@
24
5
  | Configuration | 19 | 417 | 111 | 1 | 0 | 0 | 0 |
25
6
  | Controllers | 1 | 7 | 6 | 1 | 1 | 1 | 4 |
26
7
  | Helpers | 1 | 3 | 3 | 0 | 0 | 0 | 0 |
27
- | Javascripts | 3 | 27 | 7 | 0 | 0 | 0 | 0 |
8
+ | Javascripts | 6 | 28 | 7 | 0 | 0 | 0 | 0 |
28
9
  | Jobs | 1 | 7 | 2 | 1 | 0 | 0 | 0 |
29
10
  | Libraries | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
30
11
  | Mailers | 1 | 4 | 4 | 1 | 0 | 0 | 0 |
31
12
  | Model Tests | 2 | 5 | 4 | 2 | 0 | 0 | 0 |
32
- | Models | 1 | 3 | 3 | 1 | 0 | 0 | 0 |
13
+ | Models | 4 | 10 | 10 | 4 | 0 | 0 | 0 |
33
14
  | Spec Support | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
34
15
  | Test Support | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
35
16
  +----------------------+---------+---------+---------+---------+---------+-----+-------+
36
- | Code | 30 | 477 | 145 | 7 | 1 | 0 | 143 |
17
+ | Code | 36 | 485 | 152 | 10 | 1 | 0 | 150 |
37
18
  | Tests | 4 | 7 | 6 | 2 | 0 | 0 | 0 |
38
- | Total | 34 | 484 | 151 | 9 | 1 | 0 | 149 |
19
+ | Total | 40 | 492 | 158 | 12 | 1 | 0 | 156 |
39
20
  +----------------------+---------+---------+---------+---------+---------+-----+-------+
40
- Code LOC: 145 Test LOC: 6 Code to Test Ratio: 1:0.0 Files: 34
21
+ Code LOC: 152 Test LOC: 6 Code to Test Ratio: 1:0.0 Files: 40
22
+
23
+ Polymorphic models count: 1 polymorphic associations
24
+ Schema Stats: 2 `create_table` calls in schema.rb
@@ -0,0 +1,4 @@
1
+ class CoreController
2
+ def index
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ class CoreModel
2
+ def core_method
3
+ true
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ class BillingModel
2
+ def charge
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ module Billing
2
+ end
@@ -0,0 +1,3 @@
1
+ module CoreLib
2
+ CONST = 1
3
+ end
@@ -0,0 +1 @@
1
+ enforce_dependencies: true
@@ -0,0 +1,4 @@
1
+ class Pack1Controller
2
+ def show
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ class Pack1Model
2
+ def m1
3
+ end
4
+ def m2
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ module Pack1Lib
2
+ end
@@ -0,0 +1,3 @@
1
+ enforce_dependencies: true
2
+ dependencies:
3
+ - packs/pack2
@@ -0,0 +1,4 @@
1
+ describe Pack1Model do
2
+ it "works" do
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class Pack2Model
2
+ def only
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class Pack2ModelTest
2
+ def test_it
3
+ end
4
+ end
@@ -8,13 +8,13 @@ describe RailsStats::CodeStatistics do
8
8
  describe "#to_s" do
9
9
  it "outputs useful stats for a Rails project" do
10
10
  root_directory = File.expand_path('../../../test/dummy', File.dirname(__FILE__))
11
- table = File.read(File.expand_path('../../../fixtures/console-output.txt', __FILE__))
11
+ expected_table = File.read(File.expand_path('../../../fixtures/console-output.txt', __FILE__))
12
12
 
13
- out, err = capture_io do
14
- RailsStats::CodeStatistics.new(root_directory).to_s
15
- end
13
+ expected_bundler_table, _ = capture_io { Bundler::Stats::CLI.start }
16
14
 
17
- assert_equal table.delete(" \n"), out.delete(" \n")
15
+ output, _ = capture_io { RailsStats::CodeStatistics.new(root_directory).to_s }
16
+
17
+ assert_equal([expected_bundler_table, expected_table].join, output)
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ describe RailsStats::ConsoleFormatter do
6
+ describe '#to_s' do
7
+ it 'invokes Bundler::Stats::CLI with text format' do
8
+ root_directory = File.absolute_path('./test/dummy')
9
+ calculator = RailsStats::StatsCalculator.new(root_directory)
10
+ formatter = RailsStats::ConsoleFormatter.new(calculator)
11
+
12
+ received_args = nil
13
+ original = Bundler::Stats::CLI.method(:start)
14
+ Bundler::Stats::CLI.define_singleton_method(:start) { |args = []| received_args = args }
15
+
16
+ begin
17
+ capture_io { formatter.to_s }
18
+ ensure
19
+ Bundler::Stats::CLI.singleton_class.remove_method(:start)
20
+ Bundler::Stats::CLI.define_singleton_method(:start, original)
21
+ end
22
+
23
+ assert_equal ['--format', 'text'], received_args
24
+ end
25
+
26
+ it 'prints a section per pack plus a Core and a grand total for packwerk apps' do
27
+ root_directory = File.absolute_path('./test/fixtures/packwerk_app')
28
+ calculator = RailsStats::StatsCalculator.new(root_directory)
29
+ formatter = RailsStats::ConsoleFormatter.new(calculator)
30
+
31
+ original = Bundler::Stats::CLI.method(:start)
32
+ Bundler::Stats::CLI.define_singleton_method(:start) { |args = []| nil }
33
+
34
+ begin
35
+ output, _ = capture_io { formatter.to_s }
36
+ ensure
37
+ Bundler::Stats::CLI.singleton_class.remove_method(:start)
38
+ Bundler::Stats::CLI.define_singleton_method(:start, original)
39
+ end
40
+
41
+ assert_includes output, '== Core Application =='
42
+ assert_includes output, '== Pack: packs/pack1 =='
43
+ assert_includes output, '== Pack: packs/pack2 =='
44
+ assert_includes output, '== Pack: components/billing =='
45
+ assert_includes output, '== Total (all packs) =='
46
+ end
47
+ end
48
+ end
@@ -4,141 +4,84 @@ require "test_helper"
4
4
 
5
5
  describe RailsStats::JSONFormatter do
6
6
  describe "#result" do
7
- JSON_STRING = <<~EOS
8
- [{
9
- "name": "Libraries",
10
- "files": "1",
11
- "lines": "1",
12
- "loc": "1",
13
- "classes": "0",
14
- "methods": "0",
15
- "m_over_c": "0",
16
- "loc_over_m": "0"
17
- }, {
18
- "name": "Mailers",
19
- "files": "1",
20
- "lines": "4",
21
- "loc": "4",
22
- "classes": "1",
23
- "methods": "0",
24
- "m_over_c": "0",
25
- "loc_over_m": "0"
26
- }, {
27
- "name": "Model Tests",
28
- "files": "2",
29
- "lines": "5",
30
- "loc": "4",
31
- "classes": "2",
32
- "methods": "0",
33
- "m_over_c": "0",
34
- "loc_over_m": "0"
35
- }, {
36
- "name": "Models",
37
- "files": "1",
38
- "lines": "3",
39
- "loc": "3",
40
- "classes": "1",
41
- "methods": "0",
42
- "m_over_c": "0",
43
- "loc_over_m": "0"
44
- }, {
45
- "name": "Javascripts",
46
- "files": "3",
47
- "lines": "27",
48
- "loc": "7",
49
- "classes": "0",
50
- "methods": "0",
51
- "m_over_c": "0",
52
- "loc_over_m": "0"
53
- }, {
54
- "name": "Jobs",
55
- "files": "1",
56
- "lines": "7",
57
- "loc": "2",
58
- "classes": "1",
59
- "methods": "0",
60
- "m_over_c": "0",
61
- "loc_over_m": "0"
62
- }, {
63
- "name": "Controllers",
64
- "files": "1",
65
- "lines": "7",
66
- "loc": "6",
67
- "classes": "1",
68
- "methods": "1",
69
- "m_over_c": "1",
70
- "loc_over_m": "4"
71
- }, {
72
- "name": "Helpers",
73
- "files": "1",
74
- "lines": "3",
75
- "loc": "3",
76
- "classes": "0",
77
- "methods": "0",
78
- "m_over_c": "0",
79
- "loc_over_m": "0"
80
- }, {
81
- "name": "Channels",
82
- "files": "2",
83
- "lines": "8",
84
- "loc": "8",
85
- "classes": "2",
86
- "methods": "0",
87
- "m_over_c": "0",
88
- "loc_over_m": "0"
89
- }, {
90
- "name": "Configuration",
91
- "files": "19",
92
- "lines": "417",
93
- "loc": "111",
94
- "classes": "1",
95
- "methods": "0",
96
- "m_over_c": "0",
97
- "loc_over_m": "0"
98
- }, {
99
- "name": "Spec Support",
100
- "files": "1",
101
- "lines": "1",
102
- "loc": "1",
103
- "classes": "0",
104
- "methods": "0",
105
- "m_over_c": "0",
106
- "loc_over_m": "0"
107
- }, {
108
- "name": "Test Support",
109
- "files": "1",
110
- "lines": "1",
111
- "loc": "1",
112
- "classes": "0",
113
- "methods": "0",
114
- "m_over_c": "0",
115
- "loc_over_m": "0"
116
- }, {
117
- "name": "Total",
118
- "files": "34",
119
- "lines": "484",
120
- "loc": "151",
121
- "classes": "9",
122
- "methods": "1",
123
- "m_over_c": "0",
124
- "loc_over_m": "149",
125
- "code_to_test_ratio": "0.0",
126
- "total": true
127
- }]
128
- EOS
7
+ def code_stat(name, files:, lines:, loc:, classes:, methods: "0", m_over_c: "0", loc_over_m: "0")
8
+ {"name"=>name, "files"=>files, "lines"=>lines, "loc"=>loc,
9
+ "classes"=>classes, "methods"=>methods, "m_over_c"=>m_over_c, "loc_over_m"=>loc_over_m}
10
+ end
11
+
12
+ def total_stat(name, files:, lines:, loc:, classes:, methods: "0", m_over_c: "0", loc_over_m: "0", code_to_test_ratio: "0.0")
13
+ code_stat(name, files: files, lines: lines, loc: loc, classes: classes,
14
+ methods: methods, m_over_c: m_over_c, loc_over_m: loc_over_m)
15
+ .merge("code_to_test_ratio" => code_to_test_ratio, "total" => true)
16
+ end
17
+
18
+ EXPECTED_GEM_NAMES = %w[
19
+ simplecov-console codecov rails_stats simplecov minitest-around
20
+ bundler byebug minitest minitest-spec-context
21
+ ].freeze
129
22
 
130
23
  it "outputs useful stats for a Rails project" do
131
24
  root_directory = File.absolute_path("./test/dummy")
132
25
 
133
26
  calculator = RailsStats::StatsCalculator.new(root_directory)
134
27
  formatter = RailsStats::JSONFormatter.new(calculator)
28
+ result = formatter.result
29
+
30
+ # Verify bundler-stats structure (without asserting on version-specific values)
31
+ gems_section = result.find { |h| h["gems"] }
32
+ assert gems_section, "Expected a gems section in the result"
33
+ assert gems_section["summary"], "Expected a summary in the gems section"
34
+ %w[declared unpinned total github].each do |key|
35
+ assert gems_section["summary"].key?(key), "Expected summary to have '#{key}'"
36
+ end
135
37
 
136
- sorted_expectation = JSON.parse(JSON_STRING).sort {|x, y| x["name"] <=> y["name"] }
137
- sorted_result = formatter.result.sort {|x, y| x["name"] <=> y["name"] }
38
+ gem_names = gems_section["gems"].map { |g| g["name"] }
39
+ assert_equal EXPECTED_GEM_NAMES.sort, gem_names.sort
138
40
 
139
- sorted_expectation.each_with_index do |x, i|
140
- assert_equal x, sorted_result[i]
41
+ gems_section["gems"].each do |gem|
42
+ %w[name total_dependencies first_level_dependencies top_level_dependencies transitive_dependencies].each do |key|
43
+ assert gem.key?(key), "Expected gem '#{gem["name"]}' to have '#{key}'"
44
+ end
141
45
  end
46
+
47
+ # Verify code stats (these are stable across Ruby versions)
48
+ expected_code_stats = [
49
+ code_stat("Mailers", files: "1", lines: "4", loc: "4", classes: "1"),
50
+ code_stat("Models", files: "4", lines: "10", loc: "10", classes: "4"),
51
+ code_stat("Controllers", files: "1", lines: "7", loc: "6", classes: "1", methods: "1", m_over_c: "1", loc_over_m: "4"),
52
+ code_stat("Helpers", files: "1", lines: "3", loc: "3", classes: "0"),
53
+ code_stat("Jobs", files: "1", lines: "7", loc: "2", classes: "1"),
54
+ code_stat("Channels", files: "2", lines: "8", loc: "8", classes: "2"),
55
+ code_stat("Javascripts", files: "6", lines: "28", loc: "7", classes: "0"),
56
+ code_stat("Libraries", files: "1", lines: "1", loc: "1", classes: "0"),
57
+ code_stat("Configuration", files: "19", lines: "417", loc: "111", classes: "1"),
58
+ code_stat("Model Tests", files: "2", lines: "5", loc: "4", classes: "2"),
59
+ code_stat("Spec Support", files: "1", lines: "1", loc: "1", classes: "0"),
60
+ code_stat("Test Support", files: "1", lines: "1", loc: "1", classes: "0"),
61
+ ]
62
+
63
+ code_stats = result.select { |h| h["name"] && !h["total"] }
64
+ assert_equal expected_code_stats.sort_by { |h| h["name"] }, code_stats.sort_by { |h| h["name"] }
65
+
66
+ # Verify totals
67
+ expected_totals = [
68
+ total_stat("Code", files: "36", lines: "485", loc: "152", classes: "10", methods: "1", loc_over_m: "150"),
69
+ total_stat("Tests", files: "4", lines: "7", loc: "6", classes: "2"),
70
+ total_stat("Total", files: "40", lines: "492", loc: "158", classes: "12", methods: "1", loc_over_m: "156"),
71
+ ]
72
+
73
+ totals = result.select { |h| h["total"] }
74
+ assert_equal expected_totals.sort_by { |h| h["name"] }, totals.sort_by { |h| h["name"] }
75
+
76
+ # Verify schema and polymorphic stats
77
+ schema_stats = result.find { |h| h["schema_stats"] }
78
+ assert schema_stats, "Expected schema_stats in result"
79
+ assert_equal "#{Dir.pwd}/test/dummy/db/schema.rb", schema_stats["schema_stats"]["schema_path"]
80
+ assert_equal 2, schema_stats["schema_stats"]["create_table calls count"]
81
+
82
+ polymorphic_stats = result.find { |h| h["polymorphic_stats"] }
83
+ assert polymorphic_stats, "Expected polymorphic_stats in result"
84
+ assert_equal 1, polymorphic_stats["polymorphic_stats"]["polymorphic_models_count"]
142
85
  end
143
86
  end
144
- end
87
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe RailsStats::PackFinder do
6
+ describe ".find" do
7
+ it "detects packwerk packs and component-based components" do
8
+ root = File.absolute_path("./test/fixtures/packwerk_app")
9
+
10
+ packs = RailsStats::PackFinder.find(root).map { |path| path.sub("#{root}/", "") }
11
+
12
+ assert_equal ["components/billing", "packs/pack1", "packs/pack2"], packs.sort
13
+ end
14
+
15
+ it "does not treat the root package.yml as a pack" do
16
+ root = File.absolute_path("./test/fixtures/packwerk_app")
17
+
18
+ packs = RailsStats::PackFinder.find(root)
19
+
20
+ refute_includes packs, root
21
+ end
22
+
23
+ it "returns an empty list for a plain Rails app" do
24
+ root = File.absolute_path("./test/dummy")
25
+
26
+ assert_empty RailsStats::PackFinder.find(root)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe RailsStats::StatsCalculator do
6
+ describe "#statistics_by_group" do
7
+ before do
8
+ root = File.absolute_path("./test/fixtures/packwerk_app")
9
+ @calculator = RailsStats::StatsCalculator.new(root)
10
+ end
11
+
12
+ it "reports that the app is split into packs" do
13
+ assert @calculator.packs?
14
+ end
15
+
16
+ it "groups statistics per pack/component with Core first" do
17
+ groups = @calculator.statistics_by_group.keys
18
+
19
+ assert_equal "Core", groups.first
20
+ assert_equal ["Core", "components/billing", "packs/pack1", "packs/pack2"], groups
21
+ end
22
+
23
+ it "attributes each pack's files to its own group" do
24
+ by_group = @calculator.statistics_by_group
25
+
26
+ assert_equal 1, by_group["packs/pack1"]["Models"].files_total
27
+ assert_equal 1, by_group["packs/pack1"]["Controllers"].files_total
28
+ assert_equal 1, by_group["packs/pack1"]["Model Tests"].files_total
29
+ assert_equal 1, by_group["packs/pack2"]["Models"].files_total
30
+ assert_equal 1, by_group["components/billing"]["Models"].files_total
31
+ end
32
+
33
+ it "keeps core files in the Core group" do
34
+ core = @calculator.statistics_by_group["Core"]
35
+
36
+ assert_equal 1, core["Models"].files_total
37
+ assert_equal 1, core["Controllers"].files_total
38
+ assert_equal 1, core["Libraries"].files_total
39
+ end
40
+
41
+ it "includes pack lib directories that are not gems" do
42
+ assert_equal 1, @calculator.statistics_by_group["packs/pack1"]["Libraries"].files_total
43
+ end
44
+
45
+ it "has a grand total that matches the sum of every group" do
46
+ _code, _tests, grand = @calculator.totals_for(@calculator.statistics)
47
+
48
+ per_group_files = @calculator.statistics_by_group.sum do |_group, stats|
49
+ _c, _t, group_grand = @calculator.totals_for(stats)
50
+ group_grand.files_total
51
+ end
52
+
53
+ assert_equal grand.files_total, per_group_files
54
+ end
55
+ end
56
+
57
+ describe "#packs?" do
58
+ it "is false for a plain Rails app" do
59
+ calculator = RailsStats::StatsCalculator.new(File.absolute_path("./test/dummy"))
60
+
61
+ refute calculator.packs?
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "tmpdir"
5
+ require "fileutils"
6
+
7
+ describe RailsStats::Util do
8
+ describe ".calculate_directory_statistics" do
9
+ it "ignores vendored directories and never counts a directory that looks like a source file" do
10
+ Dir.mktmpdir do |dir|
11
+ # A real source file that should be counted.
12
+ File.write(File.join(dir, "real.rb"), "class Real\nend\n")
13
+
14
+ # A directory whose name matches the source-file pattern. It must be
15
+ # walked (its contents counted) but never opened as a file itself,
16
+ # which previously raised Errno::EISDIR.
17
+ weird = File.join(dir, "weird.rb")
18
+ FileUtils.mkdir_p(weird)
19
+ File.write(File.join(weird, "inner.rb"), "class Inner\nend\n")
20
+
21
+ # A vendored tree that must be skipped entirely, including a directory
22
+ # named like a JS file (e.g. node_modules/popper.js), the exact case
23
+ # that crashed on a packs app.
24
+ popper = File.join(dir, "node_modules", "popper.js")
25
+ FileUtils.mkdir_p(popper)
26
+ File.write(File.join(popper, "index.js"), "function noop() {}\n")
27
+
28
+ stats = RailsStats::Util.calculate_directory_statistics(dir)
29
+
30
+ # real.rb + weird.rb/inner.rb, nothing under node_modules.
31
+ assert_equal 2, stats.files_total
32
+ end
33
+ end
34
+
35
+ it "skips every ignored directory name" do
36
+ RailsStats::IGNORED_DIRS.each do |ignored|
37
+ Dir.mktmpdir do |dir|
38
+ vendored = File.join(dir, ignored)
39
+ FileUtils.mkdir_p(vendored)
40
+ File.write(File.join(vendored, "ignored.rb"), "class Ignored\nend\n")
41
+
42
+ stats = RailsStats::Util.calculate_directory_statistics(dir)
43
+
44
+ assert_equal 0, stats.files_total, "expected #{ignored}/ to be skipped"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end