Skip to content

Commit 6b3b21e

Browse files
committed
🧪 Add (failing) test for Regexp#linear_time?
1 parent dfb5174 commit 6b3b21e

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

test/net/imap/regexp_collector.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
class RegexpCollector
4+
ConstantRegexp = Data.define(:mod, :const_name, :regexp) do
5+
def name = "%s::%s" % [mod, const_name]
6+
end
7+
8+
InstanceMethodRegexp = Data.define(:mod, :method_name, :regexp) do
9+
def name = "%s#%s: %p" % [mod, method_name, regexp]
10+
end
11+
12+
SingletonMethodRegexp = Data.define(:mod, :method_name, :regexp) do
13+
def name = "%s.%s: %p" % [mod, method_name, regexp]
14+
end
15+
16+
attr_reader :mod, :exclude, :exclude_map
17+
18+
def initialize(mod, exclude: [], exclude_map: {})
19+
@mod = mod
20+
@exclude = exclude
21+
@exclude_map = exclude_map
22+
end
23+
24+
def to_a = (consts + code).flat_map { collect_regexps _1 }
25+
def to_h = to_a.group_by(&:name).transform_values(&:first)
26+
27+
def excluded?(name_or_obj) =
28+
exclude&.include?(name_or_obj) || exclude_map[mod]&.include?(name_or_obj)
29+
30+
def consts
31+
@consts = mod
32+
.constants(false)
33+
.reject { excluded? _1 }
34+
.map { [_1, mod.const_get(_1)] }
35+
.select { _2 in Regexp | Module }
36+
.reject { excluded? _2 }
37+
.reject { _2 in Module and "%s::%s" % [mod, _1] != _2.name }
38+
end
39+
40+
def code
41+
return [] unless defined?(RubyVM::InstructionSequence)
42+
[
43+
*(mod.methods(false) + mod.private_methods(false))
44+
.map { mod.method _1 },
45+
*(mod.instance_methods(false) + mod.private_instance_methods(false))
46+
.map { mod.instance_method _1 },
47+
]
48+
.reject { excluded?(_1) || excluded?(_2) }
49+
end
50+
51+
protected attr_writer :mod
52+
53+
private
54+
55+
def collect_regexps(obj) = case obj
56+
in name, Module => submod then dup.tap do _1.mod = submod end.to_a
57+
in name, Regexp => regexp then ConstantRegexp.new mod, name, regexp
58+
in Method => method then method_regexps SingletonMethodRegexp, method
59+
in UnboundMethod => method then method_regexps InstanceMethodRegexp, method
60+
end
61+
62+
def method_regexps(klass, method)
63+
iseq_regexps(RubyVM::InstructionSequence.of(method))
64+
.map { klass.new mod, method.name, _1 }
65+
end
66+
67+
def iseq_regexps(obj) = case obj
68+
in RubyVM::InstructionSequence then iseq_regexps obj.to_a
69+
in Array then obj.flat_map { iseq_regexps _1 }.uniq
70+
in Regexp then excluded?(obj) ? [] : obj
71+
else []
72+
end
73+
74+
end

test/net/imap/test_regexps.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
require "net/imap"
4+
require "test/unit"
5+
6+
return unless Regexp.respond_to?(:linear_time?)
7+
8+
begin
9+
require_relative "regexp_collector"
10+
rescue LoadError
11+
warn "Can't collect regexps to test Regexp.linear_time?(...)"
12+
end
13+
14+
unless defined?(RegexpCollector)
15+
class RegexpCollector # :nodoc:
16+
def initialize(...) end
17+
def to_a; [] end
18+
def to_h; {} end
19+
end
20+
end
21+
22+
class IMAPRegexpsTest < Test::Unit::TestCase
23+
24+
data(
25+
RegexpCollector.new(
26+
Net::IMAP,
27+
exclude_map: {
28+
Net::IMAP => %i[BodyTypeAttachment BodyTypeExtension], # deprecated
29+
},
30+
exclude: [
31+
/\n(?!\z)/n, # TODO (in Net::IMAP#put_string)
32+
Net::IMAP::StringPrep::Tables::BIDI_FAILS_REQ2, # TODO
33+
Net::IMAP::StringPrep::Tables::BIDI_FAILS_REQ3, # TODO
34+
# The following regexps are built using BIDI_FAILS_REQ{2,3}
35+
Net::IMAP::StringPrep::Tables::BIDI_FAILURE,
36+
Net::IMAP::StringPrep::SASLprep::PROHIBITED,
37+
Net::IMAP::StringPrep::SASLprep::PROHIBITED_STORED,
38+
]
39+
).to_h
40+
)
41+
42+
def test_linear_time(data)
43+
assert Regexp.linear_time?(data.regexp)
44+
end
45+
46+
end

0 commit comments

Comments
 (0)