rspec-sprint 0.1.0 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f16ad949645afe84a5a8ba614a5a6e2e40f609fa76f73c2e0af72557830dbd9d
4
- data.tar.gz: 8503cde0dabd4c8cccefee9d53ef55b996e2724ad21ebcb6f4f4d6b9b6eec747
3
+ metadata.gz: 806c50acbafad7460f0a755f0bf56d6f38c7c28c9fad2726810d45bcd19c23fc
4
+ data.tar.gz: 9958794c6f2f4d456246feeb2acae5576dc12a0565f697e0791e246b1fdfe83d
5
5
  SHA512:
6
- metadata.gz: 021d7debd206e0d543a97c71692eed732b0f3bc280e13e3262c19936ee790012d3b93588f3de91f1741f19fa26ac36da322e2fd1ad87ef81f215098231f00a66
7
- data.tar.gz: 0d93b9cb8eb129e2cc6451d8ad2607efcbd0a1a6108549d4c5f8a566c4fd69db39b481d1b40293462ed94bd0396b49da6a2cf573b9e38eb3c6357c22c73a9e43
6
+ metadata.gz: 458773150de314011f601648d4d50b48bdce862e6381ea21d19393ded0b9a3ac23a3691d1333cfc670f7ae00016091d61b5013032a9ab56f3290e57fe0810e1e
7
+ data.tar.gz: 9969a1b44ed4a96cfc81262cabb41a6a86256007086211d0eb78b52263d572f929b0a7b8ff8c42764b33d56020492d22fffe766582a14f4720c491d6234175a3
@@ -21,9 +21,14 @@ module RspecSprint
21
21
  Requires test-prof in your bundle and `require "test_prof"` in your spec
22
22
  helper for factory diagnosis (Rule①); without it, the other rules still run.
23
23
  DESC
24
+ option :no_snapshot, type: :boolean, default: false,
25
+ desc: "Skip saving a snapshot to .rspec-sprint/"
26
+ option :compare_last, type: :boolean, default: false,
27
+ desc: "Show delta vs most recent previous snapshot"
24
28
  def doctor(*rspec_args)
25
29
  result = Collector.new(rspec_args: rspec_args).run
26
- puts Doctor.diagnose(result)
30
+ puts Doctor.diagnose(result, no_snapshot: options[:no_snapshot],
31
+ compare_last: options[:compare_last])
27
32
  end
28
33
 
29
34
  default_command :doctor
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module RspecSprint
6
+ # Computes and formats the delta between a previous snapshot (raw JSON hash)
7
+ # and the current Normalizer::Snapshot.
8
+ module Comparator
9
+ NO_BASELINE_MSG = "前回のスナップショットがありません。まず `bundle exec rspec-sprint doctor` を実行してベースラインを作成してください。\n" \
10
+ "(No prior snapshot. Run doctor once to create a baseline.)"
11
+
12
+ module_function
13
+
14
+ # previous_data: Hash parsed from a saved snapshot JSON (may be nil)
15
+ # current: Normalizer::Snapshot
16
+ # Returns a formatted string to append to the doctor report.
17
+ def format_delta(previous_data, current)
18
+ return NO_BASELINE_MSG if previous_data.nil?
19
+
20
+ prev_duration = previous_data[:suite_duration].to_f
21
+ curr_duration = current.suite_duration.to_f
22
+ duration_delta = curr_duration - prev_duration
23
+ duration_pct = prev_duration > 0 ? (duration_delta / prev_duration * 100).round(1) : 0.0
24
+
25
+ prev_factory_pct = prev_duration > 0 ? (previous_data[:factory_time].to_f / prev_duration * 100).round(1) : 0.0
26
+ curr_factory_pct = (current.factory_time_ratio * 100).round(1)
27
+
28
+ prev_ts = format_timestamp(previous_data[:created_at])
29
+
30
+ sign = duration_delta >= 0 ? "+" : ""
31
+ "前回比 #{sign}#{duration_delta.round(1)}s (#{sign}#{duration_pct}%) " \
32
+ "factory time #{prev_factory_pct}% → #{curr_factory_pct}% [#{prev_ts}]"
33
+ end
34
+
35
+ def format_timestamp(iso8601)
36
+ return "unknown" if iso8601.nil?
37
+
38
+ Time.parse(iso8601).strftime("%Y-%m-%d %H:%M")
39
+ rescue ArgumentError
40
+ iso8601.to_s
41
+ end
42
+ end
43
+ end
@@ -3,6 +3,8 @@
3
3
  require_relative "normalizer"
4
4
  require_relative "ranker"
5
5
  require_relative "formatter"
6
+ require_relative "snapshot_store"
7
+ require_relative "comparator"
6
8
 
7
9
  module RspecSprint
8
10
  # Pure pipeline: profiler JSON files -> normalized snapshot -> ranked findings
@@ -11,9 +13,17 @@ module RspecSprint
11
13
  module Diagnosis
12
14
  module_function
13
15
 
14
- def from_files(rspec_json:, factory_prof_json: nil)
16
+ def from_files(rspec_json:, factory_prof_json: nil, save_snapshot: false, compare_last: false)
17
+ # Load previous BEFORE saving so "last" refers to the prior run, not this one.
18
+ previous = compare_last ? SnapshotStore.load_last : nil
19
+
15
20
  snapshot = Normalizer.new(rspec_json: rspec_json, factory_prof_json: factory_prof_json).call
16
- Formatter.format(Ranker.call(snapshot))
21
+ SnapshotStore.save(snapshot) if save_snapshot
22
+
23
+ report = Formatter.format(Ranker.call(snapshot))
24
+ return report unless compare_last
25
+
26
+ [report, Comparator.format_delta(previous, snapshot)].join("\n\n")
17
27
  end
18
28
  end
19
29
  end
@@ -11,7 +11,7 @@ module RspecSprint
11
11
  module Doctor
12
12
  module_function
13
13
 
14
- def diagnose(result)
14
+ def diagnose(result, no_snapshot: false, compare_last: false)
15
15
  return diagnosis_impossible(result) unless result.rspec_json?
16
16
 
17
17
  sections = []
@@ -19,7 +19,9 @@ module RspecSprint
19
19
  sections << factory_prof_missing_hint unless result.factory_prof?
20
20
  sections << Diagnosis.from_files(
21
21
  rspec_json: result.rspec_json_path,
22
- factory_prof_json: result.factory_prof? ? result.factory_prof_path : nil
22
+ factory_prof_json: result.factory_prof? ? result.factory_prof_path : nil,
23
+ save_snapshot: !no_snapshot,
24
+ compare_last: compare_last
23
25
  )
24
26
  sections.join("\n\n")
25
27
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "time"
6
+
7
+ module RspecSprint
8
+ # Persists normalized snapshots to .rspec-sprint/snapshots/ for future
9
+ # comparison (the seed for --compare-last / Test Performance Ledger).
10
+ # Failure-safe: disk errors warn and skip; doctor never crashes because of us.
11
+ class SnapshotStore
12
+ DEFAULT_DIR = ".rspec-sprint/snapshots"
13
+ MAX_SNAPSHOTS = 3
14
+
15
+ def self.save(snapshot, dir: DEFAULT_DIR)
16
+ new(dir: dir).save(snapshot)
17
+ end
18
+
19
+ def self.load_last(dir: DEFAULT_DIR)
20
+ new(dir: dir).load_last
21
+ end
22
+
23
+ def initialize(dir: DEFAULT_DIR)
24
+ @dir = dir
25
+ end
26
+
27
+ def save(snapshot)
28
+ FileUtils.mkdir_p(@dir)
29
+ path = File.join(@dir, "#{timestamp}.json")
30
+ File.write(path, serialize(snapshot))
31
+ prune
32
+ rescue Errno::EACCES, Errno::ENOSPC => e
33
+ warn "rspec-sprint: snapshot not saved (#{e.class.name.split("::").last}: #{e.message})"
34
+ end
35
+
36
+ def list
37
+ Dir.glob(File.join(@dir, "*.json")).sort
38
+ end
39
+
40
+ # Returns the most recent snapshot as a symbol-keyed Hash, or nil if none.
41
+ def load_last
42
+ files = list
43
+ return nil if files.empty?
44
+
45
+ JSON.parse(File.read(files.last), symbolize_names: true)
46
+ rescue JSON::ParserError, Errno::ENOENT
47
+ nil
48
+ end
49
+
50
+ private
51
+
52
+ def serialize(snapshot)
53
+ JSON.pretty_generate({
54
+ schema_version: 1,
55
+ created_at: Time.now.utc.iso8601,
56
+ suite_duration: snapshot.suite_duration,
57
+ factory_time: snapshot.factory_time,
58
+ example_count: snapshot.example_count,
59
+ failure_count: snapshot.failure_count,
60
+ factories: (snapshot.factories || []).map do |f|
61
+ { name: f.name, total_count: f.total_count,
62
+ top_level_count: f.top_level_count, total_time: f.total_time }
63
+ end,
64
+ examples: (snapshot.examples || []).map do |e|
65
+ { full_description: e.full_description,
66
+ file_path: sanitize_path(e.file_path),
67
+ run_time: e.run_time, status: e.status }
68
+ end
69
+ })
70
+ end
71
+
72
+ def sanitize_path(path)
73
+ path.to_s.sub(%r{\A\./}, "")
74
+ end
75
+
76
+ def prune
77
+ files = list
78
+ return if files.length <= MAX_SNAPSHOTS
79
+
80
+ files.first(files.length - MAX_SNAPSHOTS).each do |f|
81
+ File.delete(f)
82
+ rescue Errno::EACCES
83
+ nil
84
+ end
85
+ end
86
+
87
+ def timestamp
88
+ Time.now.strftime("%Y%m%d%H%M%S%L")
89
+ end
90
+ end
91
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RspecSprint
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-sprint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yasu551
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-06-18 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: test-prof
@@ -42,6 +43,7 @@ description: |
42
43
  JSON formatter), interprets the output against opinionated heuristics, and asserts
43
44
  the top few things to fix — diagnosis plus concrete prescription, not just numbers.
44
45
  It wraps existing profilers; it does not reimplement them.
46
+ email:
45
47
  executables:
46
48
  - rspec-sprint
47
49
  extensions: []
@@ -53,6 +55,7 @@ files:
53
55
  - lib/rspec_sprint.rb
54
56
  - lib/rspec_sprint/cli.rb
55
57
  - lib/rspec_sprint/collector.rb
58
+ - lib/rspec_sprint/comparator.rb
56
59
  - lib/rspec_sprint/diagnosis.rb
57
60
  - lib/rspec_sprint/doctor.rb
58
61
  - lib/rspec_sprint/finding.rb
@@ -60,6 +63,7 @@ files:
60
63
  - lib/rspec_sprint/normalizer.rb
61
64
  - lib/rspec_sprint/ranker.rb
62
65
  - lib/rspec_sprint/rules.rb
66
+ - lib/rspec_sprint/snapshot_store.rb
63
67
  - lib/rspec_sprint/version.rb
64
68
  homepage: https://github.com/yasu551/rspec-sprint
65
69
  licenses:
@@ -67,6 +71,7 @@ licenses:
67
71
  metadata:
68
72
  source_code_uri: https://github.com/yasu551/rspec-sprint
69
73
  changelog_uri: https://github.com/yasu551/rspec-sprint/blob/main/CHANGELOG.md
74
+ post_install_message:
70
75
  rdoc_options: []
71
76
  require_paths:
72
77
  - lib
@@ -81,7 +86,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
86
  - !ruby/object:Gem::Version
82
87
  version: '0'
83
88
  requirements: []
84
- rubygems_version: 4.0.10
89
+ rubygems_version: 3.5.22
90
+ signing_key:
85
91
  specification_version: 4
86
92
  summary: Diagnose RSpec/Rails test-suite slowness and assert the top repo-specific
87
93
  fixes.