testsift 0.0.1

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/LICENSE +22 -0
  4. data/README.md +572 -0
  5. data/bin/testsift +15 -0
  6. data/lib/testsift/cache/Rakefile +43 -0
  7. data/lib/testsift/cli/blast_radius.rb +318 -0
  8. data/lib/testsift/cli/cache_clear.rb +114 -0
  9. data/lib/testsift/cli/cache_info.rb +106 -0
  10. data/lib/testsift/cli/doctor.rb +307 -0
  11. data/lib/testsift/cli/explain.rb +273 -0
  12. data/lib/testsift/cli/report_open.rb +90 -0
  13. data/lib/testsift/cli/snapshot_helpers.rb +119 -0
  14. data/lib/testsift/cli.rb +219 -0
  15. data/lib/testsift/configuration.rb +1467 -0
  16. data/lib/testsift/defaults.rb +11 -0
  17. data/lib/testsift/engine/snapshot_builder.rb +117 -0
  18. data/lib/testsift/engine.rb +896 -0
  19. data/lib/testsift/example.rb +188 -0
  20. data/lib/testsift/filter.rb +103 -0
  21. data/lib/testsift/line_stub.rb +62 -0
  22. data/lib/testsift/load_config.rb +15 -0
  23. data/lib/testsift/load_default_config.rb +46 -0
  24. data/lib/testsift/load_global_config.rb +33 -0
  25. data/lib/testsift/load_local_config.rb +17 -0
  26. data/lib/testsift/logger.rb +59 -0
  27. data/lib/testsift/rails/README.md +78 -0
  28. data/lib/testsift/rails/i18n_tracking.rb +137 -0
  29. data/lib/testsift/rails/notifications.rb +263 -0
  30. data/lib/testsift/rails/preset.rb +94 -0
  31. data/lib/testsift/rails/railtie.rb +22 -0
  32. data/lib/testsift/rails.rb +15 -0
  33. data/lib/testsift/remote_cache/README.md +140 -0
  34. data/lib/testsift/remote_cache/Rakefile +61 -0
  35. data/lib/testsift/remote_cache/archive.rb +137 -0
  36. data/lib/testsift/remote_cache/backend.rb +73 -0
  37. data/lib/testsift/remote_cache/git_ancestry.rb +241 -0
  38. data/lib/testsift/remote_cache/local_fs_backend.rb +439 -0
  39. data/lib/testsift/remote_cache/redis_backend.rb +554 -0
  40. data/lib/testsift/remote_cache/s3_backend.rb +712 -0
  41. data/lib/testsift/remote_cache/user_tasks.rb +436 -0
  42. data/lib/testsift/remote_cache/validator.rb +50 -0
  43. data/lib/testsift/remote_cache.rb +22 -0
  44. data/lib/testsift/reporters/README.md +103 -0
  45. data/lib/testsift/reporters/base.rb +87 -0
  46. data/lib/testsift/reporters/coverage_json_reporter.rb +338 -0
  47. data/lib/testsift/reporters/html/.gitignore +19 -0
  48. data/lib/testsift/reporters/html/.prettierignore +4 -0
  49. data/lib/testsift/reporters/html/.prettierrc.json +9 -0
  50. data/lib/testsift/reporters/html/README.md +80 -0
  51. data/lib/testsift/reporters/html/dist/assets/index.css +2 -0
  52. data/lib/testsift/reporters/html/dist/assets/index.js +1 -0
  53. data/lib/testsift/reporters/html/dist/index.html +24 -0
  54. data/lib/testsift/reporters/html/eslint.config.js +62 -0
  55. data/lib/testsift/reporters/html/package-lock.json +4910 -0
  56. data/lib/testsift/reporters/html/package.json +29 -0
  57. data/lib/testsift/reporters/html/src/app.jsx +130 -0
  58. data/lib/testsift/reporters/html/src/components/AllExamples.jsx +86 -0
  59. data/lib/testsift/reporters/html/src/components/DuplicateExamples.jsx +68 -0
  60. data/lib/testsift/reporters/html/src/components/ExamplesDependency.jsx +78 -0
  61. data/lib/testsift/reporters/html/src/components/FilesDependency.jsx +72 -0
  62. data/lib/testsift/reporters/html/src/components/FlakyExamples.jsx +42 -0
  63. data/lib/testsift/reporters/html/src/components/ReportTable.jsx +131 -0
  64. data/lib/testsift/reporters/html/src/components/SearchBar.jsx +19 -0
  65. data/lib/testsift/reporters/html/src/index.html +23 -0
  66. data/lib/testsift/reporters/html/src/main.jsx +37 -0
  67. data/lib/testsift/reporters/html/src/styles.css +434 -0
  68. data/lib/testsift/reporters/html/vite.config.js +42 -0
  69. data/lib/testsift/reporters/html_reporter.rb +266 -0
  70. data/lib/testsift/reporters/json_reporter.rb +88 -0
  71. data/lib/testsift/reporters/payload_builder.rb +241 -0
  72. data/lib/testsift/reporters/registry.rb +120 -0
  73. data/lib/testsift/reporters/terminal_reporter.rb +264 -0
  74. data/lib/testsift/rspec/README.md +73 -0
  75. data/lib/testsift/rspec/installation.rb +97 -0
  76. data/lib/testsift/rspec/metadata.rb +96 -0
  77. data/lib/testsift/rspec/parallel_tests.rb +459 -0
  78. data/lib/testsift/rspec/rails_setup.rb +42 -0
  79. data/lib/testsift/rspec/reporter_hook.rb +90 -0
  80. data/lib/testsift/rspec/runner_hook.rb +253 -0
  81. data/lib/testsift/source_file.rb +56 -0
  82. data/lib/testsift/storage/README.md +35 -0
  83. data/lib/testsift/storage/backend.rb +130 -0
  84. data/lib/testsift/storage/json_backend.rb +884 -0
  85. data/lib/testsift/storage/lazy_snapshot.rb +65 -0
  86. data/lib/testsift/storage/schema.rb +50 -0
  87. data/lib/testsift/storage/serializer/json.rb +41 -0
  88. data/lib/testsift/storage/serializer/msgpack.rb +167 -0
  89. data/lib/testsift/storage/snapshot.rb +141 -0
  90. data/lib/testsift/storage/sqlite_backend.rb +709 -0
  91. data/lib/testsift/time_formatter.rb +74 -0
  92. data/lib/testsift/tracker/README.md +36 -0
  93. data/lib/testsift/tracker/coverage_adapter.rb +180 -0
  94. data/lib/testsift/tracker/declared_globs.rb +100 -0
  95. data/lib/testsift/tracker/dependency_graph.rb +134 -0
  96. data/lib/testsift/tracker/env_matcher.rb +127 -0
  97. data/lib/testsift/tracker/env_snapshot.rb +77 -0
  98. data/lib/testsift/tracker/example_registry.rb +153 -0
  99. data/lib/testsift/tracker/file_digest.rb +61 -0
  100. data/lib/testsift/tracker/filter.rb +127 -0
  101. data/lib/testsift/tracker/input.rb +99 -0
  102. data/lib/testsift/tracker/io_hooks/file.rb +55 -0
  103. data/lib/testsift/tracker/io_hooks/io.rb +24 -0
  104. data/lib/testsift/tracker/io_hooks/json.rb +23 -0
  105. data/lib/testsift/tracker/io_hooks/kernel.rb +26 -0
  106. data/lib/testsift/tracker/io_hooks/yaml.rb +38 -0
  107. data/lib/testsift/tracker/io_hooks.rb +195 -0
  108. data/lib/testsift/tracker/loaded_files_tracker.rb +290 -0
  109. data/lib/testsift/tracker/new_file_detector.rb +62 -0
  110. data/lib/testsift/tracker/whole_suite_invalidators.rb +96 -0
  111. data/lib/testsift/version.rb +7 -0
  112. data/lib/testsift.rb +335 -0
  113. metadata +193 -0
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ at_exit do
4
+ # `respond_to?` guards against a crash while `lib/testsift.rb`
5
+ # is still loading (e.g. a corrupt `.testsift` raising at
6
+ # config-load time): this hook registers before the module body
7
+ # that defines `at_exit_behavior` runs, so without the guard the
8
+ # original error would be shadowed by a NoMethodError backtrace
9
+ # from the half-loaded module.
10
+ TestSift.at_exit_behavior if TestSift.respond_to?(:at_exit_behavior)
11
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestSift
4
+ class Engine
5
+ module SnapshotBuilder
6
+ private
7
+
8
+ # Internal method on the tracer pipeline.
9
+ # @api private
10
+ def build_snapshot
11
+ run_id = Digest::MD5.hexdigest(@all_examples.keys.sort.to_json)
12
+ @run_id = run_id
13
+
14
+ TestSift::Storage::Snapshot.new(
15
+ schema_version: TestSift::Storage::Schema::CURRENT,
16
+ run_id: run_id,
17
+ all_examples: @all_examples,
18
+ duplicate_examples: duplicates_for_snapshot,
19
+ interrupted_examples: @registry.ids_with_status(:interrupted),
20
+ flaky_examples: @registry.ids_with_status(:flaky),
21
+ failed_examples: @registry.ids_with_status(:failed),
22
+ pending_examples: @registry.ids_with_status(:pending),
23
+ skipped_examples: @registry.ids_with_status(:skipped),
24
+ all_files: all_files_by_name,
25
+ dependency: dependency_by_name,
26
+ reverse_dependency: reverse_dependency_by_name,
27
+ examples_coverage: merge_examples_coverage_with_previous,
28
+ boot_set: @loaded_files_tracker.boot_set_digest_snapshot,
29
+ wsi_snapshot: @whole_suite_invalidators.digest_snapshot,
30
+ env_snapshot: env_snapshot_for_persistence,
31
+ env_dependency: env_dependency_for_persistence,
32
+ cache_hit_reason: @filtered_examples.values.tally,
33
+ filtered_examples: @filtered_examples
34
+ )
35
+ end
36
+
37
+ # Merge the current run's per-example coverage with the previous
38
+ # run's. Re-run examples contribute their freshly-computed map
39
+ # (record_coverage_delta overwrote per-line strengths, so
40
+ # @examples_coverage[id] is the authoritative new coverage).
41
+ # Skipped examples don't appear in @examples_coverage, so the
42
+ # prev map carries them forward unchanged. Previously in
43
+ # seed_state_from_previous; moved here so large caches don't
44
+ # pay the full examples_coverage materialization cost at setup
45
+ # time. Preserves 1.x semantics: the saved map is the union of
46
+ # (prev minus this-run's keys) + this-run's entries.
47
+ def merge_examples_coverage_with_previous
48
+ merged = {}
49
+ if @previous_snapshot
50
+ prev = @previous_snapshot.examples_coverage
51
+ if prev.is_a?(Hash)
52
+ prev.each { |id, cov| merged[id] = cov unless @examples_coverage.key?(id) }
53
+ end
54
+ end
55
+ @examples_coverage.each { |id, cov| merged[id] = cov }
56
+ merged
57
+ end
58
+
59
+ # Snapshot only the env keys THIS run tracked - persisting keys
60
+ # that stopped being tracked (user removed `tracks: env: ...`
61
+ # between runs) would pin stale digests in the cache. Missing
62
+ # keys on the next load just trigger a one-time re-run for any
63
+ # example that reintroduces them, which is the correct behavior.
64
+ def env_snapshot_for_persistence
65
+ return {} if @env_snapshot.nil? || @tracked_env_names.empty?
66
+
67
+ @env_snapshot.digest_snapshot(@tracked_env_names)
68
+ end
69
+
70
+ # Project the per-example `@tracks_env` map (Set<env_name> per
71
+ # example_id) into a JSON-friendly Hash[id => sorted Array] for
72
+ # persistence. Reporters consume this to render the env-
73
+ # dependency view on the Examples Dependency report. Empty sets
74
+ # drop out so the on-disk map stays narrow. Sort keeps the output
75
+ # deterministic for downstream diffs and golden tests.
76
+ def env_dependency_for_persistence
77
+ result = {}
78
+ @tracks_env.each do |example_id, names|
79
+ next if names.nil? || names.empty?
80
+
81
+ result[example_id] = names.to_a.sort
82
+ end
83
+ result
84
+ end
85
+
86
+ # Internal method on the tracer pipeline.
87
+ # @api private
88
+ def duplicates_for_snapshot
89
+ @duplicate_examples.select { |_, entries| entries.count > 1 }
90
+ end
91
+
92
+ # Internal method on the tracer pipeline.
93
+ # @api private
94
+ def all_files_by_name
95
+ @all_files.each_with_object({}) do |(_, meta), acc|
96
+ acc[meta[:file_name]] = meta
97
+ end
98
+ end
99
+
100
+ # Internal method on the tracer pipeline.
101
+ # @api private
102
+ def dependency_by_name
103
+ @graph.example_ids.to_h do |id|
104
+ [id, @graph.paths_for(id).to_set { |p| path_to_file_name(p) }]
105
+ end
106
+ end
107
+
108
+ # Internal method on the tracer pipeline.
109
+ # @api private
110
+ def reverse_dependency_by_name
111
+ @graph.reverse_dependency_hash.each_with_object({}) do |(path, ids), acc|
112
+ acc[path_to_file_name(path)] = ids.to_set
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end