Skip to content

Commit 1d072c2

Browse files
authored
πŸ”€ βœ…πŸ“ˆ Move parser tests to yml; add tests, benchmark (#103)
The two big motivations for this: 1. to simplify generation of test data from RFCs or other sources. 2. to simplify re-use of test data as benchmark data. Currently defined test types: * `:parser_assert_equal`: compares parse result to the `expected` data. * `:parser_pending`: print out yaml which can be inserted into the appropriate yaml test fixture file. * `:assert_parse_failure`: asserts `ResponseParseError` is raised πŸ“ˆ `benchmarks/generate_parser_benchmarks` generates a `benchmark-driver` benchmark for every `:parser_assert_equal` test Additional notes: * βœ… Test most server response examples from RFC3501 Although there is also a lot of overlap in here, this greatly improves branch coverage of ResponseParser tests. Some of our existing tests were moved or renamed to match. * βœ… Add THREAD tests, using RFC5256 examples * πŸ“– Internally document lex states * BODY/BODYSTRUCTURE is relatively complicated, so it's kept in its own file, separate from other FETCH msg-att tests. * 🚧 The BODYSTRUCTURE tests match _current_ behavior, so the tests include some buggy behavior. * 🚧 Tests for modseq on SearchResult were commented out for now. That PR is coming soon. :) * πŸ”₯ drop test_flag_list_many_same_flags... not really an issue any more * πŸ“– Add new parser test doc * 🚚 Move uid_mapping test to new `test_imap_response_data.rb`
2 parents 9678cc6 + 26547e6 commit 1d072c2

27 files changed

+3243
-436
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env ruby
2+
3+
require "yaml"
4+
require "pathname"
5+
require "net/imap"
6+
7+
path = Pathname.new(__dir__) / "../test/net/imap/fixtures/response_parser"
8+
files = path.glob("*.yml")
9+
tests = files.flat_map {|file|
10+
file.to_s
11+
.then { YAML.unsafe_load_file _1 }
12+
.fetch(:tests)
13+
.select {|test_name, test|
14+
:parser_assert_equal == test.fetch(:test_type) {
15+
test.key?(:expected) ? :parser_assert_equal : :parser_pending
16+
}
17+
}
18+
.map {|test_name, _|
19+
[
20+
file.relative_path_from(__dir__).to_s,
21+
test_name.to_s,
22+
]
23+
}
24+
}
25+
26+
init = <<RUBY
27+
require "yaml"
28+
require "net/imap"
29+
30+
def load_response(file, name)
31+
YAML.unsafe_load_file(file).dig(:tests, name, :response) \\
32+
or abort "ERRORO: missing %p fixture data in %p" % [name, file]
33+
end
34+
35+
parser = Net::IMAP::ResponseParser.new
36+
RUBY
37+
38+
prelude = <<RUBY
39+
response = load_response(%p,
40+
%p)
41+
RUBY
42+
script = "parser.parse(response)"
43+
44+
benchmarks = tests.map {|file, fixture_name|
45+
name = fixture_name.delete_prefix("test_")
46+
{name:, prelude: prelude % [file, fixture_name], script:}
47+
.transform_keys(&:to_s)
48+
}
49+
.sort_by { _1["name"] }
50+
51+
puts YAML.dump({"prelude" => init, "benchmark" => benchmarks})

β€Žbenchmarks/parser.yml

Lines changed: 567 additions & 0 deletions
Large diffs are not rendered by default.

β€Žlib/net/imap/response_parser.rb

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,31 @@ def parse(str)
3636

3737
# :stopdoc:
3838

39-
EXPR_BEG = :EXPR_BEG
40-
EXPR_DATA = :EXPR_DATA
41-
EXPR_TEXT = :EXPR_TEXT
42-
EXPR_RTEXT = :EXPR_RTEXT
43-
EXPR_CTEXT = :EXPR_CTEXT
44-
45-
T_SPACE = :SPACE
46-
T_NIL = :NIL
47-
T_NUMBER = :NUMBER
48-
T_ATOM = :ATOM
49-
T_QUOTED = :QUOTED
50-
T_LPAR = :LPAR
51-
T_RPAR = :RPAR
52-
T_BSLASH = :BSLASH
53-
T_STAR = :STAR
54-
T_LBRA = :LBRA
55-
T_RBRA = :RBRA
56-
T_LITERAL = :LITERAL
57-
T_PLUS = :PLUS
58-
T_PERCENT = :PERCENT
59-
T_CRLF = :CRLF
60-
T_EOF = :EOF
61-
T_TEXT = :TEXT
62-
39+
EXPR_BEG = :EXPR_BEG # the default, used in most places
40+
EXPR_DATA = :EXPR_DATA # envelope, body(structure), namespaces
41+
EXPR_TEXT = :EXPR_TEXT # text, after 'resp-text-code "]"'
42+
EXPR_RTEXT = :EXPR_RTEXT # resp-text, before "["
43+
EXPR_CTEXT = :EXPR_CTEXT # resp-text-code, after 'atom SP'
44+
45+
T_SPACE = :SPACE # atom special
46+
T_ATOM = :ATOM # atom (subset of astring chars)
47+
T_NIL = :NIL # subset of atom and label
48+
T_NUMBER = :NUMBER # subset of atom
49+
T_LBRA = :LBRA # subset of atom
50+
T_PLUS = :PLUS # subset of atom; tag special
51+
T_RBRA = :RBRA # atom special; resp_special; valid astring char
52+
T_QUOTED = :QUOTED # starts/end with atom special
53+
T_BSLASH = :BSLASH # atom special; quoted special
54+
T_LPAR = :LPAR # atom special; paren list delimiter
55+
T_RPAR = :RPAR # atom special; paren list delimiter
56+
T_STAR = :STAR # atom special; list wildcard
57+
T_PERCENT = :PERCENT # atom special; list wildcard
58+
T_LITERAL = :LITERAL # starts with atom special
59+
T_CRLF = :CRLF # atom special; text special; quoted special
60+
T_TEXT = :TEXT # any char except CRLF
61+
T_EOF = :EOF # end of response string
62+
63+
# the default, used in most places
6364
BEG_REGEXP = /\G(?:\
6465
(?# 1: SPACE )( +)|\
6566
(?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
@@ -78,6 +79,7 @@ def parse(str)
7879
(?# 15: CRLF )(\r\n)|\
7980
(?# 16: EOF )(\z))/ni
8081

82+
# envelope, body(structure), namespaces
8183
DATA_REGEXP = /\G(?:\
8284
(?# 1: SPACE )( )|\
8385
(?# 2: NIL )(NIL)|\
@@ -87,13 +89,16 @@ def parse(str)
8789
(?# 6: LPAR )(\()|\
8890
(?# 7: RPAR )(\)))/ni
8991

92+
# text, after 'resp-text-code "]"'
9093
TEXT_REGEXP = /\G(?:\
9194
(?# 1: TEXT )([^\x00\r\n]*))/ni
9295

96+
# resp-text, before "["
9397
RTEXT_REGEXP = /\G(?:\
9498
(?# 1: LBRA )(\[)|\
9599
(?# 2: TEXT )([^\x00\r\n]*))/ni
96100

101+
# resp-text-code, after 'atom SP'
97102
CTEXT_REGEXP = /\G(?:\
98103
(?# 1: TEXT )([^\x00\r\n\]]*))/ni
99104

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
:tests:
3+
test_acl_response:
4+
:comment: |
5+
[Bug #8281]
6+
:response: "* ACL \"INBOX/share\" \"[email protected]\"
7+
lrswickxteda\r\n"
8+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
9+
name: ACL
10+
data:
11+
- !ruby/struct:Net::IMAP::MailboxACLItem
12+
13+
rights: lrswickxteda
14+
mailbox: INBOX/share
15+
raw_data: "* ACL \"INBOX/share\" \"[email protected]\"
16+
lrswickxteda\r\n"

0 commit comments

Comments
Β (0)