-
Notifications
You must be signed in to change notification settings - Fork 250
Expand file tree
/
Copy pathconstant_drift_test.rb
More file actions
77 lines (69 loc) · 2.94 KB
/
Copy pathconstant_drift_test.rb
File metadata and controls
77 lines (69 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
require_relative "test_helper"
# Guards against "constant drift" between the RBS core signatures and the actual
# runtime, in both directions:
#
# * a constant declared in RBS but no longer defined by Ruby (e.g.
# `Float::ROUNDS`, removed in Ruby 3.0), and
# * a constant defined by Ruby but missing from RBS.
#
# Only platform- and build-invariant core classes are hard-gated here. Classes
# whose constant set legitimately varies by OS or build options (`Process`,
# `Socket`, `Errno`, `Signal`, `File::Constants`, `RbConfig`, `Etc`, ...) are
# intentionally excluded: their RBS declarations cannot match any single
# platform's runtime. `Object`/`BasicObject`/`Kernel` are excluded too, since
# every top-level class shows up under `Object.constants`.
class ConstantDriftTest < Test::Unit::TestCase
# Platform/build-invariant core classes and modules whose declared constant
# set must match the runtime exactly.
HARD_GATE = [
Float, Integer, Numeric, Rational, Complex,
Math, Comparable,
String, Symbol,
Array, Hash, Range, Struct,
NilClass, TrueClass, FalseClass
].freeze
# Known, intentional exceptions keyed by "::Name" => [:CONST, ...]. Use this
# for build- or platform-conditional constants (and `private_constant`s) that
# are legitimately undeclared, so the gate stays green across CI platforms
# without being weakened elsewhere.
SKIP = {
# Defined only when Ruby is built with GMP (USE_GMP): present on the Linux
# CI build, absent on e.g. macOS.
"::Integer" => [:GMP_VERSION]
}.freeze
def env
StdlibTest::DEFAULT_ENV
end
# Constants declared directly under `type_name` in the loaded RBS environment
# (plain constants plus nested classes/modules and their aliases), matching
# what `Module#constants(false)` returns at runtime.
def rbs_constants(type_name)
prefix = "#{type_name}::"
names = []
[env.constant_decls, env.class_decls, env.class_alias_decls].each do |store|
store.each_key do |tn|
s = tn.to_s
next unless s.start_with?(prefix)
rest = s.delete_prefix(prefix)
names << rest.to_sym unless rest.include?("::")
end
end
names.uniq.sort
end
HARD_GATE.each do |klass|
define_method(:"test_no_constant_drift_#{klass.name.gsub("::", "_")}") do
type_name = "::#{klass.name}"
skip = SKIP[type_name] || []
runtime = (klass.constants(false) - skip).sort
declared = (rbs_constants(type_name) - skip).sort
stale = declared - runtime
missing = runtime - declared
assert_empty stale,
"RBS declares #{type_name} constants that no longer exist at runtime: #{stale.inspect}. " \
"Remove them from the signature (or add to ConstantDriftTest::SKIP if intentional)."
assert_empty missing,
"Runtime defines #{type_name} constants missing from RBS: #{missing.inspect}. " \
"Add them to the signature (or add to ConstantDriftTest::SKIP if intentional)."
end
end
end