-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathextract_test_metadata.rb
102 lines (84 loc) · 2.98 KB
/
extract_test_metadata.rb
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class TestRunner
class ExtractTestMetadata < Parser::AST::Processor
include Mandate
initialize_with :filelines, :test_node, :index
def call
@ignore_line_numbers = []
# Generate the data by taking a look at the AST.
# This method causes multiple calls of the on_send
# one for each time a method is invoked in the test class.
# For example, when we call assert, or skip
process(test_node)
# Now all the data is collected, just piece it together
# into a nice hash which can be merged into the test results
{
test: test_identifier,
name: test_name,
test_code:,
index:,
task_id:
}
end
def on_send(node)
# This method will get called for all the nodes that get called on the
# test class. We only care about skip lines for now, so leave the others alone.
return unless node.method_name == :skip
# Add any line numbers covered by the skip to the ignore block
@ignore_line_numbers += (node.first_line..node.last_line).to_a
end
private
attr_reader :ignore_line_numbers
memoize
def test_identifier
test_node.method_name.to_s
end
# TODO: Make this a bit prettier.
memoize
def test_name
test_node.method_name.to_s.gsub("test_", "").
tr("_", " ").capitalize
end
# This builds up the command part. Simply put the algorithm is:
# - All the lines of codes that we haven't chosen to ignore
# - Plus an bits we've chosen to add back as the test_code part
memoize
def test_code
# Map through those lines, skipping any that were
# part of assertions
test_code = body_line_numbers.map do |idx|
next if ignore_line_numbers.include?(idx)
c = code_for_line(idx)
# Only return if it's not a skip comment
c.start_with?(/\s*#\s*skip/, /\s*###\s*task_id/) ? nil : c
end.compact.join("").rstrip
# Align everything to the left as the final step
clean_leading_whitespace(test_code)
end
memoize
def task_id
body_line_numbers.map do |idx|
next if ignore_line_numbers.include?(idx)
c = code_for_line(idx)
# Find a line started with `### task_id` and get the number
ch_task_id = /\s*###\s*task_id/
c.chomp.strip.delete('### task_id:').to_i if c.start_with?(ch_task_id)
end.compact.first
end
# Remove the minimum amount of leading whitespace
# from all lines
def clean_leading_whitespace(multiline)
min = multiline.lines.map { |line| line[/^\s*/].size }.min
multiline.gsub(/^\s{#{min}}/, '')
end
# The parser returns 1-indexed line numbers. This
# function retrieves them based on their 0-based equiv.
def code_for_line(one_indexed_idx)
filelines[one_indexed_idx - 1]
end
memoize
def body_line_numbers
# Get the lines excluding the first (def) and last (end)
((test_node.first_line + 1)..(test_node.last_line - 1))
end
end
end