smartest 0.2.0 → 0.2.1.alpha1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9891230284d4770c57adc2ec2d28356aca9323ba07cd825c6a7f2fb219d6415a
4
- data.tar.gz: 2cd3e5badccdc21d42a9b3ffd303053e75aa886dcaffefcb903cf84fc6483c90
3
+ metadata.gz: cbe5852f8932b55c528baa05ddcdaad39071557e43707e03ab99b5acc9e13ede
4
+ data.tar.gz: 8c0d1165cab6932fe337623d9429b18d201b97f25b5f5dcf55724179e4036271
5
5
  SHA512:
6
- metadata.gz: 1fe246549fe47ebb020820690b66937c6fa558be8444e8641f2c176a3ef9886370c17a260b25598cba8d3817adb37cdf331d7de7348c82561dedc993c8dc303e
7
- data.tar.gz: bcb7e23e7fb872e3001d6683577981a03d5c5482d1d548d9f8f07d1e4f3b3d5766d08b4b3ccbb2c8f17be85c9c553e84c2f2983732e8ecc2966509704839f792
6
+ metadata.gz: 8610801f162fca9e54e542db7141ba0b69269d1e02339dd212690dcc89b768cc3acd72a0fdc2b4dd8471a680db7f135d3829131a085277c3fcdbd79eda202000
7
+ data.tar.gz: ab6ce118ab140fc1bfe8eb2d2667ed19d5b9d56c657ebf53166217e721e20ddcdab3018dd22e05cb0cfc5403cce92c817d7f311a549879bbb9fe9239a748463a
data/exe/smartest CHANGED
@@ -9,11 +9,13 @@ usage = <<~USAGE
9
9
  Usage:
10
10
  smartest [paths...]
11
11
  smartest path/to/test_file.rb:line[-line]
12
+ smartest --profile [N]
12
13
  smartest --init
13
14
  smartest --version
14
15
  smartest --help
15
16
 
16
17
  When no paths are given, Smartest loads smartest/**/*_test.rb.
18
+ --profile prints the slowest tests after the run (default 5).
17
19
  USAGE
18
20
 
19
21
  command = :run
@@ -45,7 +47,8 @@ begin
45
47
  load File.expand_path(file)
46
48
  end
47
49
 
48
- exit Smartest::Runner.new(tests: arguments.select_tests(Smartest.suite.tests)).run
50
+ reporter = Smartest::Reporter.new($stdout, profile_count: arguments.profile_count)
51
+ exit Smartest::Runner.new(tests: arguments.select_tests(Smartest.suite.tests), reporter: reporter).run
49
52
  rescue Exception => error
50
53
  raise if Smartest.fatal_exception?(error)
51
54
 
@@ -4,14 +4,18 @@ require "set"
4
4
 
5
5
  module Smartest
6
6
  class CLIArguments
7
- attr_reader :files, :line_filters
7
+ DEFAULT_PROFILE_COUNT = 5
8
+
9
+ attr_reader :files, :line_filters, :profile_count
8
10
 
9
11
  def initialize(argv)
10
12
  @files = []
11
13
  @whole_files = Set.new
12
14
  @line_filters = Hash.new { |hash, key| hash[key] = Set.new }
15
+ @profile_count = nil
13
16
 
14
- parse(argv.empty? ? ["smartest/**/*_test.rb"] : argv)
17
+ paths = extract_options(argv)
18
+ parse_paths(paths.empty? ? ["smartest/**/*_test.rb"] : paths)
15
19
  end
16
20
 
17
21
  def filter_tests?
@@ -32,8 +36,37 @@ module Smartest
32
36
 
33
37
  private
34
38
 
35
- def parse(argv)
36
- argv.each do |argument|
39
+ def extract_options(argv)
40
+ paths = []
41
+ index = 0
42
+
43
+ while index < argv.length
44
+ argument = argv[index]
45
+
46
+ case argument
47
+ when "--profile"
48
+ next_argument = argv[index + 1]
49
+ if next_argument && next_argument.match?(/\A\d+\z/)
50
+ @profile_count = next_argument.to_i
51
+ index += 2
52
+ else
53
+ @profile_count = DEFAULT_PROFILE_COUNT
54
+ index += 1
55
+ end
56
+ when /\A--profile=(\d+)\z/
57
+ @profile_count = Regexp.last_match(1).to_i
58
+ index += 1
59
+ else
60
+ paths << argument
61
+ index += 1
62
+ end
63
+ end
64
+
65
+ paths
66
+ end
67
+
68
+ def parse_paths(paths)
69
+ paths.each do |argument|
37
70
  pattern, line_filter = split_line_filter(argument)
38
71
  matches = Dir[pattern]
39
72
  files = matches.empty? ? [pattern] : matches
@@ -7,8 +7,9 @@ module Smartest
7
7
  SKIP_MARK = "-"
8
8
  PENDING_MARK = "*"
9
9
 
10
- def initialize(io = $stdout)
10
+ def initialize(io = $stdout, profile_count: nil)
11
11
  @io = io
12
+ @profile_count = profile_count
12
13
  end
13
14
 
14
15
  def start(count)
@@ -28,6 +29,7 @@ module Smartest
28
29
  report_failures(failures) if failures.any?
29
30
  report_suite_errors(suite_errors) if suite_errors.any?
30
31
  report_suite_cleanup_errors(suite_cleanup_errors) if suite_cleanup_errors.any?
32
+ report_profile(results) if @profile_count && @profile_count.positive?
31
33
 
32
34
  @io.puts
33
35
  summary = "#{results.count} #{results.count == 1 ? 'test' : 'tests'}, #{results.count(&:passed?)} passed, #{failures.count} failed"
@@ -99,6 +101,37 @@ module Smartest
99
101
  end
100
102
  end
101
103
 
104
+ def report_profile(results)
105
+ timed = results.select { |result| result.duration }
106
+ return if timed.empty?
107
+
108
+ count = [@profile_count, timed.length].min
109
+ slowest = timed.sort_by { |result| -result.duration }.first(count)
110
+ total_duration = timed.sum(&:duration)
111
+ slowest_duration = slowest.sum(&:duration)
112
+ percent = total_duration.positive? ? (slowest_duration / total_duration) * 100 : 0.0
113
+
114
+ @io.puts
115
+ @io.puts "Top #{count} slowest #{count == 1 ? 'test' : 'tests'} " \
116
+ "(#{format_duration(slowest_duration)} seconds, #{format('%.1f', percent)}% of total time):"
117
+
118
+ slowest.each do |result|
119
+ @io.puts " #{result.test_case.name}"
120
+ location_suffix = profile_location_suffix(result.test_case.location)
121
+ @io.puts " #{format_duration(result.duration)} seconds#{location_suffix}"
122
+ end
123
+ end
124
+
125
+ def profile_location_suffix(location)
126
+ return "" unless location
127
+
128
+ " #{location.path}:#{location.lineno}"
129
+ end
130
+
131
+ def format_duration(seconds)
132
+ format("%.5f", seconds)
133
+ end
134
+
102
135
  def report_location(location)
103
136
  return unless location
104
137
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1.alpha1"
5
5
  end
@@ -1569,9 +1569,141 @@ test("cli prints help") do
1569
1569
  expect(stdout).to include("smartest [paths...]")
1570
1570
  expect(stdout).to include("smartest path/to/test_file.rb:line[-line]")
1571
1571
  expect(stdout).to include("smartest --init")
1572
+ expect(stdout).to include("smartest --profile [N]")
1572
1573
  expect(stdout).to include("smartest/**/*_test.rb")
1573
1574
  end
1574
1575
 
1576
+ test("--profile prints the slowest tests with default count of 5") do
1577
+ output = StringIO.new
1578
+ reporter = Smartest::Reporter.new(output, profile_count: 5)
1579
+
1580
+ results = [
1581
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("alpha", proc {}), duration: 0.10),
1582
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("bravo", proc {}), duration: 0.50),
1583
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("charlie", proc {}), duration: 0.30),
1584
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("delta", proc {}), duration: 0.20),
1585
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("echo", proc {}), duration: 0.40),
1586
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("foxtrot", proc {}), duration: 0.05)
1587
+ ]
1588
+
1589
+ reporter.finish(results)
1590
+ text = output.string
1591
+
1592
+ expect(text).to include("Top 5 slowest tests")
1593
+ expect(text).to include("bravo")
1594
+ expect(text).to include("echo")
1595
+ expect(text).to include("charlie")
1596
+ expect(text).to include("delta")
1597
+ expect(text).to include("alpha")
1598
+ expect(text).not_to include("foxtrot\n 0")
1599
+
1600
+ bravo_index = text.index("bravo")
1601
+ echo_index = text.index("echo")
1602
+ charlie_index = text.index("charlie")
1603
+ delta_index = text.index("delta")
1604
+ alpha_index = text.index("alpha")
1605
+ expect(bravo_index < echo_index).to eq(true)
1606
+ expect(echo_index < charlie_index).to eq(true)
1607
+ expect(charlie_index < delta_index).to eq(true)
1608
+ expect(delta_index < alpha_index).to eq(true)
1609
+ end
1610
+
1611
+ test("--profile N shows top N slowest tests") do
1612
+ output = StringIO.new
1613
+ reporter = Smartest::Reporter.new(output, profile_count: 2)
1614
+
1615
+ results = [
1616
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("alpha", proc {}), duration: 0.10),
1617
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("bravo", proc {}), duration: 0.50),
1618
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("charlie", proc {}), duration: 0.30)
1619
+ ]
1620
+
1621
+ reporter.finish(results)
1622
+ text = output.string
1623
+
1624
+ expect(text).to include("Top 2 slowest tests")
1625
+ expect(text).to include("bravo")
1626
+ expect(text).to include("charlie")
1627
+ expect(text).not_to include(" alpha\n")
1628
+ end
1629
+
1630
+ test("--profile does not error when fewer tests than the requested count") do
1631
+ output = StringIO.new
1632
+ reporter = Smartest::Reporter.new(output, profile_count: 5)
1633
+
1634
+ results = [
1635
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("solo", proc {}), duration: 0.01),
1636
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("duo", proc {}), duration: 0.02)
1637
+ ]
1638
+
1639
+ reporter.finish(results)
1640
+ text = output.string
1641
+
1642
+ expect(text).to include("Top 2 slowest tests")
1643
+ expect(text).to include("solo")
1644
+ expect(text).to include("duo")
1645
+ end
1646
+
1647
+ test("--profile is not printed when profile_count is nil") do
1648
+ output = StringIO.new
1649
+ reporter = Smartest::Reporter.new(output)
1650
+
1651
+ reporter.finish([
1652
+ Smartest::TestResult.passed(test_case: SmartestSelfTest.test_case("alpha", proc {}), duration: 0.10)
1653
+ ])
1654
+
1655
+ expect(output.string).not_to include("slowest")
1656
+ end
1657
+
1658
+ test("--profile is not printed when there are no results") do
1659
+ output = StringIO.new
1660
+ reporter = Smartest::Reporter.new(output, profile_count: 5)
1661
+
1662
+ reporter.finish([])
1663
+
1664
+ expect(output.string).not_to include("slowest")
1665
+ end
1666
+
1667
+ test("CLIArguments parses --profile in various forms") do
1668
+ expect(Smartest::CLIArguments.new(["--profile"]).profile_count).to eq(5)
1669
+ expect(Smartest::CLIArguments.new(["--profile", "3"]).profile_count).to eq(3)
1670
+ expect(Smartest::CLIArguments.new(["--profile=7"]).profile_count).to eq(7)
1671
+ expect(Smartest::CLIArguments.new([]).profile_count).to eq(nil)
1672
+ expect(Smartest::CLIArguments.new(["--profile", "smartest/foo_test.rb"]).profile_count).to eq(5)
1673
+ end
1674
+
1675
+ test("cli runs with --profile and prints slowest tests") do
1676
+ Dir.mktmpdir do |dir|
1677
+ smartest_dir = File.join(dir, "smartest")
1678
+ FileUtils.mkdir_p(smartest_dir)
1679
+ File.write(File.join(smartest_dir, "sample_test.rb"), <<~RUBY)
1680
+ require "smartest/autorun"
1681
+
1682
+ test("first") do
1683
+ expect(1).to eq(1)
1684
+ end
1685
+
1686
+ test("second") do
1687
+ expect(1).to eq(1)
1688
+ end
1689
+ RUBY
1690
+
1691
+ stdout, stderr, status = Open3.capture3(
1692
+ { "RUBYLIB" => File.expand_path("../lib", __dir__) },
1693
+ "ruby",
1694
+ File.expand_path("../exe/smartest", __dir__),
1695
+ "--profile",
1696
+ "1",
1697
+ chdir: dir
1698
+ )
1699
+
1700
+ expect(status.success?).to eq(true)
1701
+ expect(stderr).to eq("")
1702
+ expect(stdout).to include("Top 1 slowest test")
1703
+ expect(stdout).to include("2 tests, 2 passed, 0 failed")
1704
+ end
1705
+ end
1706
+
1575
1707
  test("cli initializes a runnable test scaffold") do
1576
1708
  Dir.mktmpdir do |dir|
1577
1709
  stdout, stderr, status = Open3.capture3(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Iwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-27 00:00:00.000000000 Z
11
+ date: 2026-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -91,9 +91,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
91
  version: '3.1'
92
92
  required_rubygems_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - ">"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: 1.3.1
97
97
  requirements: []
98
98
  rubygems_version: 3.4.19
99
99
  signing_key: