Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions test/fixtures/console-output.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,3 @@
+-----------------------|------------|----------------+
| Name | Total Deps | 1st Level Deps |
+-----------------------|------------|----------------+
| simplecov-console | 8 | 3 |
| codecov | 4 | 1 |
| rails_stats | 4 | 2 |
| simplecov | 3 | 3 |
| minitest-around | 1 | 1 |
| bundler | 0 | 0 |
| byebug | 0 | 0 |
| minitest | 0 | 0 |
| minitest-spec-context | 0 | 0 |
+-----------------------|------------|----------------+

Declared Gems 9
Total Gems 18
Unpinned Versions 6
Github Refs 0

@JuanVqz JuanVqz Mar 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby 3.3/3.4 resolves:
simplecov-console (0.9.2)
minitest-around (0.5.0)
terminal-table 3.0.2 → unicode-display_width (< 3)
(no unicode-emoji)
Total Gems: 17

Ruby 3.0/3.1 resolves:
simplecov-console (0.9.5)
minitest-around (0.6.0)
terminal-table → unicode-display_width (< 4)
→ unicode-emoji (~> 4.1)
Total Gems: 18

The reason is that bundle install picks different gem versions depending on the Ruby version. newer Ruby gets newer gem versions with different dependency trees. For example, terminal-table on older Ruby
pulls in unicode-emoji as an extra transitive dependency, which adds one more gem to the total count and changes the dependency numbers for simplecov-console.

That's why the bundler-stats portion of the output can't be pinned to a single expected value across the CI matrix; also, the tests didn't check it previously.


+----------------------+---------+---------+---------+---------+---------+-----+-------+
| Name | Files | Lines | LOC | Classes | Methods | M/C | LOC/M |
+----------------------+---------+---------+---------+---------+---------+-----+-------+
Expand Down
6 changes: 5 additions & 1 deletion test/lib/rails_stats/code_statistics_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
RailsStats::CodeStatistics.new(root_directory).to_s
end

# Compare only the code stats portion of the output, skipping the
# bundler-stats gem table which varies across Ruby versions.
code_stats_output = out[out.index("+----------------------+")..-1]

assert_equal(
table.lines.map(&:rstrip).join,
out.lines.map(&:rstrip).join
code_stats_output.lines.map(&:rstrip).join
)
Comment thread
JuanVqz marked this conversation as resolved.
Outdated
end
end
Expand Down
336 changes: 65 additions & 271 deletions test/lib/rails_stats/json_formatter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,290 +4,84 @@

describe RailsStats::JSONFormatter do
describe "#result" do
JSON_STRING = <<~EOS
[{
"summary": {
"declared": 9,
"unpinned": 6,
"total": 18,
"github": 0
},
"gems": [
{
"name": "simplecov-console",
"total_dependencies": 8,
"first_level_dependencies": 3,
"top_level_dependencies": {},
"transitive_dependencies": [
"ansi (>= 0)",
"simplecov (>= 0)",
"terminal-table (>= 0)",
"docile (~> 1.1)",
"simplecov-html (~> 0.11)",
"simplecov_json_formatter (~> 0.1)",
"unicode-display_width (>= 1.1.1, < 4)",
"unicode-emoji (~> 4.0, >= 4.0.4)"
]
},
{
"name": "codecov",
"total_dependencies": 4,
"first_level_dependencies": 1,
"top_level_dependencies": {},
"transitive_dependencies": [
"simplecov (>= 0.15, < 0.22)",
"docile (~> 1.1)",
"simplecov-html (~> 0.11)",
"simplecov_json_formatter (~> 0.1)"
]
},
{
"name": "rails_stats",
"total_dependencies": 4,
"first_level_dependencies": 2,
"top_level_dependencies": {},
"transitive_dependencies": [
"bundler-stats (>= 2.1)",
"rake (>= 0)",
"bundler (>= 1.9, < 3)",
"thor (>= 0.19.0, < 2.0)"
]
},
{
"name": "simplecov",
"total_dependencies": 3,
"first_level_dependencies": 3,
"top_level_dependencies": {
"codecov": "codecov (0.6.0)",
"simplecov-console": "simplecov-console (0.9.3)"
},
"transitive_dependencies": [
"docile (~> 1.1)",
"simplecov-html (~> 0.11)",
"simplecov_json_formatter (~> 0.1)"
]
},
{
"name": "minitest-around",
"total_dependencies": 1,
"first_level_dependencies": 1,
"top_level_dependencies": {},
"transitive_dependencies": [
"minitest (~> 5.0)"
]
},
{
"name": "bundler",
"total_dependencies": 0,
"first_level_dependencies": 0,
"top_level_dependencies": {
"bundler-stats": "bundler-stats (2.4.0)",
"rails_stats": "rails_stats (2.0.1)"
},
"transitive_dependencies": []
},
{
"name": "byebug",
"total_dependencies": 0,
"first_level_dependencies": 0,
"top_level_dependencies": {},
"transitive_dependencies": []
},
{
"name": "minitest",
"total_dependencies": 0,
"first_level_dependencies": 0,
"top_level_dependencies": {
"minitest-around": "minitest-around (0.5.0)"
},
"transitive_dependencies": []
},
{
"name": "minitest-spec-context",
"total_dependencies": 0,
"first_level_dependencies": 0,
"top_level_dependencies": {},
"transitive_dependencies": []
}
]
},{
"name": "Mailers",
"files": "1",
"lines": "4",
"loc": "4",
"classes": "1",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Helpers",
"files": "1",
"lines": "3",
"loc": "3",
"classes": "0",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Jobs",
"files": "1",
"lines": "7",
"loc": "2",
"classes": "1",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Controllers",
"files": "1",
"lines": "7",
"loc": "6",
"classes": "1",
"methods": "1",
"m_over_c": "1",
"loc_over_m": "4"
}, {
"name": "Models",
"files": "4",
"lines": "10",
"loc": "10",
"classes": "4",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Channels",
"files": "2",
"lines": "8",
"loc": "8",
"classes": "2",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Javascripts",
"files": "6",
"lines": "28",
"loc": "7",
"classes": "0",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Libraries",
"files": "1",
"lines": "1",
"loc": "1",
"classes": "0",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Configuration",
"files": "19",
"lines": "417",
"loc": "111",
"classes": "1",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Model Tests",
"files": "2",
"lines": "5",
"loc": "4",
"classes": "2",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Spec Support",
"files": "1",
"lines": "1",
"loc": "1",
"classes": "0",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Test Support",
"files": "1",
"lines": "1",
"loc": "1",
"classes": "0",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0"
}, {
"name": "Code",
"files": "36",
"lines": "485",
"loc": "152",
"classes": "10",
"methods": "1",
"m_over_c": "0",
"loc_over_m": "150",
"code_to_test_ratio": "0.0",
"total": true
}, {
"name": "Tests",
"files": "4",
"lines": "7",
"loc": "6",
"classes": "2",
"methods": "0",
"m_over_c": "0",
"loc_over_m": "0",
"code_to_test_ratio": "0.0",
"total": true
}, {
"name": "Total",
"files": "40",
"lines": "492",
"loc": "158",
"classes": "12",
"methods": "1",
"m_over_c": "0",
"loc_over_m": "156",
"code_to_test_ratio": "0.0",
"total": true},
{"schema_stats":
{"schema_path":
"#{Dir.pwd}/test/dummy/db/schema.rb",
"create_table calls count": 2}},
{"polymorphic_stats": {"polymorphic_models_count": 1}
}
]
EOS
def code_stat(name, files:, lines:, loc:, classes:, methods: "0", m_over_c: "0", loc_over_m: "0")
{"name"=>name, "files"=>files, "lines"=>lines, "loc"=>loc,
"classes"=>classes, "methods"=>methods, "m_over_c"=>m_over_c, "loc_over_m"=>loc_over_m}
end

def total_stat(name, files:, lines:, loc:, classes:, methods: "0", m_over_c: "0", loc_over_m: "0", code_to_test_ratio: "0.0")
code_stat(name, files: files, lines: lines, loc: loc, classes: classes,
methods: methods, m_over_c: m_over_c, loc_over_m: loc_over_m)
.merge("code_to_test_ratio" => code_to_test_ratio, "total" => true)
end

EXPECTED_GEM_NAMES = %w[
simplecov-console codecov rails_stats simplecov minitest-around
bundler byebug minitest minitest-spec-context
].freeze

it "outputs useful stats for a Rails project" do
root_directory = File.absolute_path("./test/dummy")

calculator = RailsStats::StatsCalculator.new(root_directory)
formatter = RailsStats::JSONFormatter.new(calculator)

expectation = JSON.parse(JSON_STRING)
result = formatter.result

[expectation, result].each do |data|
data.each do |hash|
next unless hash["gems"]

hash["gems"].each do |gem|
next unless gem["transitive_dependencies"]
# Verify bundler-stats structure (without asserting on version-specific values)
gems_section = result.find { |h| h["gems"] }
assert gems_section, "Expected a gems section in the result"
assert gems_section["summary"], "Expected a summary in the gems section"
%w[declared unpinned total github].each do |key|
assert gems_section["summary"].key?(key), "Expected summary to have '#{key}'"
end

gem["transitive_dependencies"].map! do |dep|
name, constraints = dep.split(/[()]/)
next dep unless constraints
gem_names = gems_section["gems"].map { |g| g["name"] }
assert_equal EXPECTED_GEM_NAMES.sort, gem_names.sort

normalized_constraints = constraints.split(/,\s*/).sort.join(', ')
"#{name}(#{normalized_constraints})"
end.sort!
end
gems_section["gems"].each do |gem|
%w[name total_dependencies first_level_dependencies top_level_dependencies transitive_dependencies].each do |key|
assert gem.key?(key), "Expected gem '#{gem["name"]}' to have '#{key}'"
end
end

assert_equal expectation, result
# Verify code stats (these are stable across Ruby versions)
expected_code_stats = [
code_stat("Mailers", files: "1", lines: "4", loc: "4", classes: "1"),
code_stat("Models", files: "4", lines: "10", loc: "10", classes: "4"),
code_stat("Controllers", files: "1", lines: "7", loc: "6", classes: "1", methods: "1", m_over_c: "1", loc_over_m: "4"),
code_stat("Helpers", files: "1", lines: "3", loc: "3", classes: "0"),
code_stat("Jobs", files: "1", lines: "7", loc: "2", classes: "1"),
code_stat("Channels", files: "2", lines: "8", loc: "8", classes: "2"),
code_stat("Javascripts", files: "6", lines: "28", loc: "7", classes: "0"),
code_stat("Libraries", files: "1", lines: "1", loc: "1", classes: "0"),
code_stat("Configuration", files: "19", lines: "417", loc: "111", classes: "1"),
code_stat("Model Tests", files: "2", lines: "5", loc: "4", classes: "2"),
code_stat("Spec Support", files: "1", lines: "1", loc: "1", classes: "0"),
code_stat("Test Support", files: "1", lines: "1", loc: "1", classes: "0"),
]

code_stats = result.select { |h| h["name"] && !h["total"] }
assert_equal expected_code_stats.sort_by { |h| h["name"] }, code_stats.sort_by { |h| h["name"] }

# Verify totals
expected_totals = [
total_stat("Code", files: "36", lines: "485", loc: "152", classes: "10", methods: "1", loc_over_m: "150"),
total_stat("Tests", files: "4", lines: "7", loc: "6", classes: "2"),
total_stat("Total", files: "40", lines: "492", loc: "158", classes: "12", methods: "1", loc_over_m: "156"),
]

totals = result.select { |h| h["total"] }
assert_equal expected_totals.sort_by { |h| h["name"] }, totals.sort_by { |h| h["name"] }

# Verify schema and polymorphic stats
schema_stats = result.find { |h| h["schema_stats"] }
assert schema_stats, "Expected schema_stats in result"
assert_equal "#{Dir.pwd}/test/dummy/db/schema.rb", schema_stats["schema_stats"]["schema_path"]
assert_equal 2, schema_stats["schema_stats"]["create_table calls count"]

polymorphic_stats = result.find { |h| h["polymorphic_stats"] }
assert polymorphic_stats, "Expected polymorphic_stats in result"
assert_equal 1, polymorphic_stats["polymorphic_stats"]["polymorphic_models_count"]
end
end
end