constable-rails 0.1.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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +88 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +515 -0
  5. data/exe/constable +7 -0
  6. data/lib/constable/case.rb +336 -0
  7. data/lib/constable/cli.rb +475 -0
  8. data/lib/constable/cold_case/minitest.rb +342 -0
  9. data/lib/constable/cold_case/rspec.rb +334 -0
  10. data/lib/constable/cold_case.rb +280 -0
  11. data/lib/constable/config.rb +125 -0
  12. data/lib/constable/coverage.rb +951 -0
  13. data/lib/constable/diff.rb +212 -0
  14. data/lib/constable/dsl.rb +833 -0
  15. data/lib/constable/identity.rb +121 -0
  16. data/lib/constable/importer/modernizer.rb +860 -0
  17. data/lib/constable/importer/reopener.rb +468 -0
  18. data/lib/constable/importer.rb +51 -0
  19. data/lib/constable/investigation.rb +67 -0
  20. data/lib/constable/isolation.rb +171 -0
  21. data/lib/constable/jail.rb +399 -0
  22. data/lib/constable/log_router.rb +197 -0
  23. data/lib/constable/matchers.rb +834 -0
  24. data/lib/constable/order_audit.rb +130 -0
  25. data/lib/constable/rails_support.rb +213 -0
  26. data/lib/constable/railtie.rb +36 -0
  27. data/lib/constable/registry.rb +57 -0
  28. data/lib/constable/reporter.rb +625 -0
  29. data/lib/constable/result.rb +149 -0
  30. data/lib/constable/runner.rb +697 -0
  31. data/lib/constable/selection.rb +205 -0
  32. data/lib/constable/storage/adapter.rb +91 -0
  33. data/lib/constable/storage/mysql_adapter.rb +125 -0
  34. data/lib/constable/storage/postgres_adapter.rb +125 -0
  35. data/lib/constable/storage/sqlite_adapter.rb +84 -0
  36. data/lib/constable/storage.rb +847 -0
  37. data/lib/constable/version.rb +5 -0
  38. data/lib/constable/warrants.rb +290 -0
  39. data/lib/constable-rails.rb +16 -0
  40. data/lib/constable.rb +151 -0
  41. data/lib/generators/constable/base.rb +99 -0
  42. data/lib/generators/constable/channel/channel_generator.rb +20 -0
  43. data/lib/generators/constable/channel/templates/channel_case.rb.tt +29 -0
  44. data/lib/generators/constable/controller/controller_generator.rb +25 -0
  45. data/lib/generators/constable/controller/templates/controller_case.rb.tt +32 -0
  46. data/lib/generators/constable/generator/generator_generator.rb +31 -0
  47. data/lib/generators/constable/generator/templates/generator_case.rb.tt +28 -0
  48. data/lib/generators/constable/helper/helper_generator.rb +23 -0
  49. data/lib/generators/constable/helper/templates/helper_case.rb.tt +19 -0
  50. data/lib/generators/constable/import_generator.rb +137 -0
  51. data/lib/generators/constable/install_generator.rb +188 -0
  52. data/lib/generators/constable/integration/integration_generator.rb +27 -0
  53. data/lib/generators/constable/integration/templates/request_case.rb.tt +22 -0
  54. data/lib/generators/constable/job/job_generator.rb +20 -0
  55. data/lib/generators/constable/job/templates/job_case.rb.tt +33 -0
  56. data/lib/generators/constable/mailbox/mailbox_generator.rb +20 -0
  57. data/lib/generators/constable/mailbox/templates/mailbox_case.rb.tt +26 -0
  58. data/lib/generators/constable/mailer/mailer_generator.rb +32 -0
  59. data/lib/generators/constable/mailer/templates/mailer_case.rb.tt +34 -0
  60. data/lib/generators/constable/mailer/templates/preview.rb.tt +14 -0
  61. data/lib/generators/constable/model/model_generator.rb +31 -0
  62. data/lib/generators/constable/model/templates/model_case.rb.tt +37 -0
  63. data/lib/generators/constable/resource/resource_generator.rb +27 -0
  64. data/lib/generators/constable/scaffold/scaffold_generator.rb +42 -0
  65. data/lib/generators/constable/scaffold/templates/api_controller_case.rb.tt +54 -0
  66. data/lib/generators/constable/scaffold/templates/controller_case.rb.tt +70 -0
  67. data/lib/generators/constable/scaffold/templates/system_case.rb.tt +53 -0
  68. data/lib/generators/constable/system/system_generator.rb +20 -0
  69. data/lib/generators/constable/system/templates/system_case.rb.tt +18 -0
  70. data/lib/generators/constable/templates/authenticatable.rb.tt +31 -0
  71. data/lib/generators/constable/templates/case_helper.rb.tt +179 -0
  72. data/lib/generators/constable/templates/config.yml.tt +67 -0
  73. data/lib/generators/constable/templates/example_case.rb.tt +56 -0
  74. data/lib/generators/constable/templates/matchers.rb.tt +36 -0
  75. data/lib/generators/constable/templates/rubocop.yml.tt +12 -0
  76. metadata +209 -0
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Constable
4
+ # The outcome of a single test, native or cold. Everything downstream -- the reporter,
5
+ # flake history, jail, warrants -- consumes these and nothing else.
6
+ class Result
7
+ STATUSES = %i[passed failed jailed skipped errored warranted parole_violation].freeze
8
+
9
+ GLYPHS = {
10
+ passed: "✓", # check
11
+ failed: "✗", # ballot x
12
+ jailed: "⛓", # chains
13
+ skipped: "○", # hollow circle
14
+ errored: "!",
15
+ warranted: "⚖", # scales
16
+ parole_violation: "⛓"
17
+ }.freeze
18
+
19
+ attr_reader :identity, :case_name, :description, :file, :line, :kind, :tier
20
+ attr_accessor :status, :duration, :failure, :warnings, :retries, :jail_reason,
21
+ :parole_day, :times_jailed, :seed, :coverage
22
+
23
+ def initialize(identity:, case_name:, description:, file:, line:, kind: :native, tier: nil,
24
+ status: :passed, duration: 0.0, failure: nil, warnings: [], retries: [])
25
+ @identity = identity
26
+ @case_name = case_name
27
+ @description = description
28
+ @file = file
29
+ @line = line
30
+ @kind = kind
31
+ @tier = tier
32
+ @status = status
33
+ @duration = duration
34
+ @failure = failure
35
+ @warnings = warnings
36
+ @retries = retries
37
+ end
38
+
39
+ def self.from_investigation(investigation, **attrs)
40
+ new(
41
+ identity: investigation.identity,
42
+ case_name: investigation.case_name,
43
+ description: investigation.full_description,
44
+ file: investigation.relative_file,
45
+ line: investigation.line,
46
+ kind: investigation.kind,
47
+ tier: investigation.tier,
48
+ **attrs
49
+ )
50
+ end
51
+
52
+ def passed? = @status == :passed
53
+ def failed? = %i[failed errored].include?(@status)
54
+ def jailed? = %i[jailed parole_violation].include?(@status)
55
+ def skipped? = @status == :skipped
56
+ def native? = @kind == :native
57
+ def cold? = @kind == :cold
58
+ def parole_violation? = @status == :parole_violation
59
+ def warranted? = @status == :warranted
60
+
61
+ def glyph = GLYPHS.fetch(@status, "?")
62
+ def location = "#{@file}:#{@line}"
63
+ def display_label = "#{@case_name} \"#{@description}\""
64
+
65
+ # The command that reruns exactly this test, seed included, ready to paste.
66
+ def rerun_command
67
+ base = "constable test #{location}"
68
+ base += " --unsafe" if cold?
69
+ base += " --seed #{@seed}" if @seed
70
+ base
71
+ end
72
+
73
+ def to_h
74
+ {
75
+ identity: @identity, case_name: @case_name, description: @description,
76
+ file: @file, line: @line, kind: @kind, tier: @tier, status: @status,
77
+ duration: @duration, failure: @failure&.to_h, warnings: @warnings,
78
+ retries: @retries, jail_reason: @jail_reason, parole_day: @parole_day,
79
+ times_jailed: @times_jailed, seed: @seed
80
+ }
81
+ end
82
+
83
+ def self.from_h(hash)
84
+ hash = hash.transform_keys(&:to_sym)
85
+ result = new(
86
+ identity: hash[:identity], case_name: hash[:case_name], description: hash[:description],
87
+ file: hash[:file], line: hash[:line], kind: (hash[:kind] || :native).to_sym,
88
+ tier: hash[:tier], status: (hash[:status] || :passed).to_sym,
89
+ duration: hash[:duration].to_f, warnings: hash[:warnings] || [], retries: hash[:retries] || []
90
+ )
91
+ result.failure = Failure.from_h(hash[:failure]) if hash[:failure]
92
+ result.jail_reason = hash[:jail_reason]
93
+ result.parole_day = hash[:parole_day]
94
+ result.times_jailed = hash[:times_jailed]
95
+ result.seed = hash[:seed]
96
+ result
97
+ end
98
+ end
99
+
100
+ # A failure carries its own context -- the assertion's actual message plus whatever
101
+ # the matcher thought was worth showing (a response body, a record's attributes) --
102
+ # and points at the investigate block, never at framework internals.
103
+ class Failure
104
+ attr_reader :message, :context, :backtrace, :exception_class
105
+
106
+ def initialize(message:, context: nil, backtrace: [], exception_class: nil)
107
+ # Other engines wrap their messages in blank lines for their own reporters. Ours
108
+ # already puts the message in a block of its own, so the padding just leaves holes.
109
+ @message = message.is_a?(String) ? message.strip : message
110
+ @context = context
111
+ @backtrace = Array(backtrace)
112
+ @exception_class = exception_class
113
+ end
114
+
115
+ def self.from_exception(error, context: nil)
116
+ new(
117
+ message: error.message,
118
+ context: context,
119
+ backtrace: Backtrace.clean(error.backtrace),
120
+ exception_class: error.class.name
121
+ )
122
+ end
123
+
124
+ def to_h
125
+ { message: @message, context: @context, backtrace: @backtrace, exception_class: @exception_class }
126
+ end
127
+
128
+ def self.from_h(hash)
129
+ return nil if hash.nil?
130
+
131
+ hash = hash.transform_keys(&:to_sym)
132
+ new(message: hash[:message], context: hash[:context],
133
+ backtrace: hash[:backtrace] || [], exception_class: hash[:exception_class])
134
+ end
135
+ end
136
+
137
+ # Strips the framework out of backtraces. A developer wants their own line, not ours.
138
+ module Backtrace
139
+ GEM_ROOT = File.expand_path("../..", __dir__)
140
+
141
+ module_function
142
+
143
+ def clean(backtrace)
144
+ lines = Array(backtrace)
145
+ app = lines.reject { |l| l.to_s.start_with?(GEM_ROOT) || l.to_s.include?("/gems/") || l.to_s.include?("/ruby/3") }
146
+ (app.empty? ? lines : app).first(10)
147
+ end
148
+ end
149
+ end