lucid 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +30 -10
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +15 -0
- data/Gemfile +4 -2
- data/HISTORY.md +22 -0
- data/{LICENSE.txt → LICENSE} +6 -3
- data/README.md +22 -8
- data/Rakefile +2 -1
- data/bin/lucid +10 -10
- data/bin/lucid-gen +4 -0
- data/lib/autotest/discover.rb +11 -0
- data/lib/autotest/lucid.rb +6 -0
- data/lib/autotest/lucid_mixin.rb +135 -0
- data/lib/autotest/lucid_rails.rb +6 -0
- data/lib/autotest/lucid_rails_rspec.rb +6 -0
- data/lib/autotest/lucid_rails_rspec2.rb +6 -0
- data/lib/autotest/lucid_rspec.rb +6 -0
- data/lib/autotest/lucid_rspec2.rb +6 -0
- data/lib/lucid.rb +32 -1
- data/lib/lucid/ast.rb +20 -0
- data/lib/lucid/ast/background.rb +116 -0
- data/lib/lucid/ast/comment.rb +24 -0
- data/lib/lucid/ast/doc_string.rb +44 -0
- data/lib/lucid/ast/empty_background.rb +33 -0
- data/lib/lucid/ast/examples.rb +49 -0
- data/lib/lucid/ast/feature.rb +99 -0
- data/lib/lucid/ast/has_steps.rb +74 -0
- data/lib/lucid/ast/location.rb +41 -0
- data/lib/lucid/ast/multiline_argument.rb +31 -0
- data/lib/lucid/ast/names.rb +13 -0
- data/lib/lucid/ast/outline_table.rb +194 -0
- data/lib/lucid/ast/scenario.rb +103 -0
- data/lib/lucid/ast/scenario_outline.rb +144 -0
- data/lib/lucid/ast/specs.rb +38 -0
- data/lib/lucid/ast/step.rb +122 -0
- data/lib/lucid/ast/step_collection.rb +92 -0
- data/lib/lucid/ast/step_invocation.rb +196 -0
- data/lib/lucid/ast/table.rb +730 -0
- data/lib/lucid/ast/tags.rb +28 -0
- data/lib/lucid/ast/tdl_walker.rb +195 -0
- data/lib/lucid/cli/app.rb +78 -0
- data/lib/lucid/cli/configuration.rb +261 -0
- data/lib/lucid/cli/options.rb +463 -0
- data/lib/lucid/cli/profile.rb +101 -0
- data/lib/lucid/configuration.rb +53 -0
- data/lib/lucid/core_ext/disable_autorunners.rb +15 -0
- data/lib/lucid/core_ext/instance_exec.rb +70 -0
- data/lib/lucid/core_ext/proc.rb +36 -0
- data/lib/lucid/core_ext/string.rb +9 -0
- data/lib/lucid/errors.rb +40 -0
- data/lib/lucid/factory.rb +43 -0
- data/lib/lucid/formatter/ansicolor.rb +168 -0
- data/lib/lucid/formatter/console.rb +218 -0
- data/lib/lucid/formatter/debug.rb +33 -0
- data/lib/lucid/formatter/duration.rb +11 -0
- data/lib/lucid/formatter/gherkin_formatter_adapter.rb +94 -0
- data/lib/lucid/formatter/gpretty.rb +24 -0
- data/lib/lucid/formatter/html.rb +610 -0
- data/lib/lucid/formatter/interceptor.rb +66 -0
- data/lib/lucid/formatter/io.rb +31 -0
- data/lib/lucid/formatter/jquery-min.js +154 -0
- data/lib/lucid/formatter/json.rb +19 -0
- data/lib/lucid/formatter/json_pretty.rb +10 -0
- data/lib/lucid/formatter/junit.rb +177 -0
- data/lib/lucid/formatter/lucid.css +283 -0
- data/lib/lucid/formatter/lucid.sass +244 -0
- data/lib/lucid/formatter/ordered_xml_markup.rb +24 -0
- data/lib/lucid/formatter/progress.rb +95 -0
- data/lib/lucid/formatter/rerun.rb +91 -0
- data/lib/lucid/formatter/standard.rb +235 -0
- data/lib/lucid/formatter/stepdefs.rb +14 -0
- data/lib/lucid/formatter/steps.rb +49 -0
- data/lib/lucid/formatter/summary.rb +35 -0
- data/lib/lucid/formatter/unicode.rb +53 -0
- data/lib/lucid/formatter/usage.rb +132 -0
- data/lib/lucid/generator.rb +21 -0
- data/lib/lucid/generators/project.rb +70 -0
- data/lib/lucid/generators/project/Gemfile.tt +6 -0
- data/lib/lucid/generators/project/browser-symbiont.rb +24 -0
- data/lib/lucid/generators/project/driver-symbiont.rb +4 -0
- data/lib/lucid/generators/project/errors.rb +26 -0
- data/lib/lucid/generators/project/events-symbiont.rb +36 -0
- data/lib/lucid/generators/project/lucid-symbiont.yml +6 -0
- data/lib/lucid/interface.rb +8 -0
- data/lib/lucid/interface_methods.rb +125 -0
- data/lib/lucid/interface_rb/matcher.rb +108 -0
- data/lib/lucid/interface_rb/rb_hook.rb +18 -0
- data/lib/lucid/interface_rb/rb_language.rb +190 -0
- data/lib/lucid/interface_rb/rb_lucid.rb +119 -0
- data/lib/lucid/interface_rb/rb_step_definition.rb +122 -0
- data/lib/lucid/interface_rb/rb_transform.rb +57 -0
- data/lib/lucid/interface_rb/rb_world.rb +136 -0
- data/lib/lucid/interface_rb/regexp_argument_matcher.rb +21 -0
- data/lib/lucid/load_path.rb +13 -0
- data/lib/lucid/parser.rb +2 -126
- data/lib/lucid/platform.rb +27 -0
- data/lib/lucid/rspec/allow_doubles.rb +20 -0
- data/lib/lucid/rspec/disallow_options.rb +27 -0
- data/lib/lucid/runtime.rb +200 -0
- data/lib/lucid/runtime/facade.rb +60 -0
- data/lib/lucid/runtime/interface_io.rb +60 -0
- data/lib/lucid/runtime/orchestrator.rb +218 -0
- data/lib/lucid/runtime/results.rb +64 -0
- data/lib/lucid/runtime/specs_loader.rb +79 -0
- data/lib/lucid/spec_file.rb +112 -0
- data/lib/lucid/step_definition_light.rb +20 -0
- data/lib/lucid/step_definitions.rb +13 -0
- data/lib/lucid/step_match.rb +99 -0
- data/lib/lucid/tdl_builder.rb +282 -0
- data/lib/lucid/term/ansicolor.rb +118 -0
- data/lib/lucid/unit.rb +11 -0
- data/lib/lucid/wire_support/configuration.rb +38 -0
- data/lib/lucid/wire_support/connection.rb +61 -0
- data/lib/lucid/wire_support/request_handler.rb +32 -0
- data/lib/lucid/wire_support/wire_exception.rb +32 -0
- data/lib/lucid/wire_support/wire_language.rb +54 -0
- data/lib/lucid/wire_support/wire_packet.rb +34 -0
- data/lib/lucid/wire_support/wire_protocol.rb +43 -0
- data/lib/lucid/wire_support/wire_protocol/requests.rb +125 -0
- data/lib/lucid/wire_support/wire_step_definition.rb +26 -0
- data/lucid.gemspec +25 -14
- metadata +220 -12
- data/lib/lucid/app.rb +0 -103
- data/lib/lucid/options.rb +0 -168
- data/lib/lucid/version.rb +0 -3
- data/lucid.yml +0 -8
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Lucid
|
4
|
+
class Runtime
|
5
|
+
|
6
|
+
module InterfaceIO
|
7
|
+
attr_writer :visitor
|
8
|
+
|
9
|
+
def puts(*messages)
|
10
|
+
@visitor.puts(*messages)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ask(question, timeout_seconds)
|
14
|
+
STDOUT.puts(question)
|
15
|
+
STDOUT.flush
|
16
|
+
puts(question)
|
17
|
+
|
18
|
+
if(Lucid::JRUBY)
|
19
|
+
answer = jruby_gets(timeout_seconds)
|
20
|
+
else
|
21
|
+
answer = mri_gets(timeout_seconds)
|
22
|
+
end
|
23
|
+
|
24
|
+
if(answer)
|
25
|
+
puts(answer)
|
26
|
+
answer
|
27
|
+
else
|
28
|
+
raise("Lucid waited for input for #{timeout_seconds} seconds, then timed out.")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def embed(src, mime_type, label)
|
33
|
+
@visitor.embed(src, mime_type, label)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def mri_gets(timeout_seconds)
|
39
|
+
begin
|
40
|
+
Timeout.timeout(timeout_seconds) do
|
41
|
+
STDIN.gets
|
42
|
+
end
|
43
|
+
rescue Timeout::Error
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def jruby_gets(timeout_seconds)
|
49
|
+
answer = nil
|
50
|
+
t = java.lang.Thread.new do
|
51
|
+
answer = STDIN.gets
|
52
|
+
end
|
53
|
+
t.start
|
54
|
+
t.join(timeout_seconds * 1000)
|
55
|
+
answer
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'lucid/factory'
|
2
|
+
require 'lucid/ast/multiline_argument'
|
3
|
+
require 'lucid/runtime/facade'
|
4
|
+
|
5
|
+
module Lucid
|
6
|
+
|
7
|
+
class Runtime
|
8
|
+
|
9
|
+
class Orchestrator
|
10
|
+
|
11
|
+
require 'forwardable'
|
12
|
+
class StepInvoker
|
13
|
+
include Gherkin::Rubify
|
14
|
+
|
15
|
+
def initialize(orchestrator)
|
16
|
+
@orchestrator = orchestrator
|
17
|
+
end
|
18
|
+
|
19
|
+
def uri(uri)
|
20
|
+
end
|
21
|
+
|
22
|
+
def step(step)
|
23
|
+
@orchestrator.invoke(step.name, AST::MultilineArgument.from(step.doc_string || step.rows))
|
24
|
+
end
|
25
|
+
|
26
|
+
def eof
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
include ObjectFactory
|
31
|
+
|
32
|
+
def initialize(user_interface, configuration={})
|
33
|
+
@configuration = Configuration.parse(configuration)
|
34
|
+
@runtime_facade = Runtime::Facade.new(self, user_interface)
|
35
|
+
@unsupported_languages = []
|
36
|
+
@supported_languages = []
|
37
|
+
@language_map = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def configure(new_configuration)
|
41
|
+
@configuration = Configuration.parse(new_configuration)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Invokes a series of steps +steps_text+. Example:
|
45
|
+
#
|
46
|
+
# invoke(%Q{
|
47
|
+
# Given I have 8 cukes in my belly
|
48
|
+
# Then I should not be thirsty
|
49
|
+
# })
|
50
|
+
def invoke_steps(steps_text, i18n, file_colon_line)
|
51
|
+
file, line = file_colon_line.split(':')
|
52
|
+
parser = Gherkin::Parser::Parser.new(StepInvoker.new(self), true, 'steps', false, i18n.iso_code)
|
53
|
+
parser.parse(steps_text, file, line.to_i)
|
54
|
+
end
|
55
|
+
|
56
|
+
def invoke(step_name, multiline_argument=nil)
|
57
|
+
multiline_argument = Lucid::AST::MultilineArgument.from(multiline_argument)
|
58
|
+
# It is very important to leave multiline_argument=nil as a vararg. Cuke4Duke needs it that way.
|
59
|
+
begin
|
60
|
+
step_match(step_name).invoke(multiline_argument)
|
61
|
+
rescue Exception => e
|
62
|
+
e.nested! if Undefined === e
|
63
|
+
raise e
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The orchestrator will register the the code language and load up an
|
68
|
+
# implementation of that language. There is a provision to make sure
|
69
|
+
# that the language is not already registered.
|
70
|
+
def load_code_language(code)
|
71
|
+
return @language_map[code] if @language_map[code]
|
72
|
+
lucid_language = create_object_of("Lucid::Interface#{code.capitalize}::#{code.capitalize}Language")
|
73
|
+
language = lucid_language.new(@runtime_facade)
|
74
|
+
@supported_languages << language
|
75
|
+
@language_map[code] = language
|
76
|
+
language
|
77
|
+
end
|
78
|
+
|
79
|
+
# The orchestrator will load only the loadable execution context files.
|
80
|
+
# This is how the orchestrator will, quite literally, orchestrate the
|
81
|
+
# execution of specs with the code logic that supports those specs.
|
82
|
+
# @see Lucid::Runtime.load_execution_context
|
83
|
+
def load_files(files)
|
84
|
+
log.info("Orchestrator Load Files:\n")
|
85
|
+
files.each do |file|
|
86
|
+
load_file(file)
|
87
|
+
end
|
88
|
+
log.info("\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
def load_files_from_paths(paths)
|
92
|
+
files = paths.map { |path| Dir["#{path}/**/*"] }.flatten
|
93
|
+
load_files files
|
94
|
+
end
|
95
|
+
|
96
|
+
def unmatched_step_definitions
|
97
|
+
@supported_languages.map do |programming_language|
|
98
|
+
programming_language.unmatched_step_definitions
|
99
|
+
end.flatten
|
100
|
+
end
|
101
|
+
|
102
|
+
def matcher_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
|
103
|
+
load_code_language('rb') if unknown_programming_language?
|
104
|
+
@supported_languages.map do |programming_language|
|
105
|
+
programming_language.matcher_text(step_keyword, step_name, multiline_arg_class, @configuration.matcher_type)
|
106
|
+
end.join("\n")
|
107
|
+
end
|
108
|
+
|
109
|
+
def unknown_programming_language?
|
110
|
+
@supported_languages.empty?
|
111
|
+
end
|
112
|
+
|
113
|
+
def fire_hook(name, *args)
|
114
|
+
@supported_languages.each do |programming_language|
|
115
|
+
programming_language.send(name, *args)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def around(scenario, block)
|
120
|
+
@supported_languages.reverse.inject(block) do |blk, programming_language|
|
121
|
+
proc do
|
122
|
+
programming_language.around(scenario) do
|
123
|
+
blk.call(scenario)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end.call
|
127
|
+
end
|
128
|
+
|
129
|
+
def step_definitions
|
130
|
+
@supported_languages.map do |programming_language|
|
131
|
+
programming_language.step_definitions
|
132
|
+
end.flatten
|
133
|
+
end
|
134
|
+
|
135
|
+
def step_match(step_name, name_to_report=nil) #:nodoc:
|
136
|
+
@match_cache ||= {}
|
137
|
+
|
138
|
+
match = @match_cache[[step_name, name_to_report]]
|
139
|
+
return match if match
|
140
|
+
|
141
|
+
@match_cache[[step_name, name_to_report]] = step_match_without_cache(step_name, name_to_report)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def step_match_without_cache(step_name, name_to_report=nil)
|
147
|
+
matches = matches(step_name, name_to_report)
|
148
|
+
raise Undefined.new(step_name) if matches.empty?
|
149
|
+
matches = best_matches(step_name, matches) if matches.size > 1 && guess_step_matches?
|
150
|
+
raise Ambiguous.new(step_name, matches, guess_step_matches?) if matches.size > 1
|
151
|
+
matches[0]
|
152
|
+
end
|
153
|
+
|
154
|
+
def guess_step_matches?
|
155
|
+
@configuration.guess?
|
156
|
+
end
|
157
|
+
|
158
|
+
def matches(step_name, name_to_report)
|
159
|
+
@supported_languages.map do |programming_language|
|
160
|
+
programming_language.step_matches(step_name, name_to_report).to_a
|
161
|
+
end.flatten
|
162
|
+
end
|
163
|
+
|
164
|
+
def best_matches(step_name, step_matches) #:nodoc:
|
165
|
+
no_groups = step_matches.select {|step_match| step_match.args.length == 0}
|
166
|
+
max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
|
167
|
+
top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
|
168
|
+
|
169
|
+
if no_groups.any?
|
170
|
+
longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
|
171
|
+
no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
|
172
|
+
elsif top_groups.any?
|
173
|
+
shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
|
174
|
+
top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
|
175
|
+
else
|
176
|
+
top_groups
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# For each execution context file, the orchestrator will determine the
|
181
|
+
# code language associated with the file.
|
182
|
+
def load_file(file)
|
183
|
+
if language = get_language_for(file)
|
184
|
+
log.info(" * #{file}\n")
|
185
|
+
language.load_code_file(file)
|
186
|
+
else
|
187
|
+
log.info(" * #{file} [NOT SUPPORTED]\n")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def log
|
192
|
+
Lucid.logger
|
193
|
+
end
|
194
|
+
|
195
|
+
# The orchestrator will attempt to get the programming language for a
|
196
|
+
# specific code file, unless that code file is marked as being an
|
197
|
+
# unsupported language. An object is returned if the code file was part
|
198
|
+
# of a supported language. If an object is returned it will be an
|
199
|
+
# object of this sort:
|
200
|
+
# Lucid::InterfaceRb::RbLanguage
|
201
|
+
def get_language_for(file)
|
202
|
+
if extension = File.extname(file)[1..-1]
|
203
|
+
return nil if @unsupported_languages.index(extension)
|
204
|
+
begin
|
205
|
+
load_code_language(extension)
|
206
|
+
rescue LoadError => e
|
207
|
+
log.info("Unable to load '#{extension}' language for file #{file}: #{e.message}\n")
|
208
|
+
@unsupported_languages << extension
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
else
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Lucid
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
class Results
|
5
|
+
def initialize(configuration)
|
6
|
+
@configuration = configuration
|
7
|
+
|
8
|
+
# Optimization - quicker lookup.
|
9
|
+
@inserted_steps = {}
|
10
|
+
@inserted_scenarios = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure(new_configuration)
|
14
|
+
@configuration = Configuration.parse(new_configuration)
|
15
|
+
end
|
16
|
+
|
17
|
+
def step_visited(step) #:nodoc:
|
18
|
+
step_id = step.object_id
|
19
|
+
|
20
|
+
unless @inserted_steps.has_key?(step_id)
|
21
|
+
@inserted_steps[step_id] = step
|
22
|
+
steps.push(step)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def scenario_visited(scenario) #:nodoc:
|
27
|
+
scenario_id = scenario.object_id
|
28
|
+
|
29
|
+
unless @inserted_scenarios.has_key?(scenario_id)
|
30
|
+
@inserted_scenarios[scenario_id] = scenario
|
31
|
+
scenarios.push(scenario)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def steps(status = nil) #:nodoc:
|
36
|
+
@steps ||= []
|
37
|
+
if(status)
|
38
|
+
@steps.select{|step| step.status == status}
|
39
|
+
else
|
40
|
+
@steps
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def scenarios(status = nil) #:nodoc:
|
45
|
+
@scenarios ||= []
|
46
|
+
if(status)
|
47
|
+
@scenarios.select{|scenario| scenario.status == status}
|
48
|
+
else
|
49
|
+
@scenarios
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def failure?
|
54
|
+
if @configuration.wip?
|
55
|
+
scenarios(:passed).any?
|
56
|
+
else
|
57
|
+
scenarios(:failed).any? || steps(:failed).any? ||
|
58
|
+
(@configuration.strict? && (steps(:undefined).any? || steps(:pending).any?))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'lucid/errors'
|
2
|
+
|
3
|
+
module Lucid
|
4
|
+
class Runtime
|
5
|
+
class SpecsLoader
|
6
|
+
include Formatter::Duration
|
7
|
+
|
8
|
+
def initialize(spec_files, filters, tag_expression)
|
9
|
+
@spec_files, @filters, @tag_expression = spec_files, filters, tag_expression
|
10
|
+
end
|
11
|
+
|
12
|
+
# @see Lucid::Runtime.specs
|
13
|
+
def specs
|
14
|
+
load unless (defined? @specs) and @specs
|
15
|
+
@specs
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# The specs loader will call upon load to load up all specs that were
|
21
|
+
# found in the spec repository. During this process, a Specs instance
|
22
|
+
# is created that will hold instances of the high level construct,
|
23
|
+
# which is basically the feature.
|
24
|
+
def load
|
25
|
+
specs = AST::Specs.new
|
26
|
+
|
27
|
+
# Note that "specs" is going to be an instance of Lucid::AST::Specs.
|
28
|
+
# It will contain a @specs instance variable that is going to contain
|
29
|
+
# an specs found.
|
30
|
+
|
31
|
+
tag_counts = {}
|
32
|
+
start = Time.new
|
33
|
+
log.info("Specs:\n")
|
34
|
+
|
35
|
+
@spec_files.each do |f|
|
36
|
+
spec_file = SpecFile.new(f)
|
37
|
+
|
38
|
+
# The "spec_file" will contain a Lucid::SpecFile instance, a
|
39
|
+
# primary attribute of which will be a @location instance variable.
|
40
|
+
|
41
|
+
spec = spec_file.parse(@filters, tag_counts)
|
42
|
+
|
43
|
+
# The "spec" will contain an instance of Lucid::AST::Feature.
|
44
|
+
|
45
|
+
if spec
|
46
|
+
specs.add_feature(spec)
|
47
|
+
log.info(" * #{f}\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
duration = Time.now - start
|
52
|
+
log.info("Parsing spec files took #{format_duration(duration)}\n\n")
|
53
|
+
|
54
|
+
check_tag_limits(tag_counts)
|
55
|
+
|
56
|
+
@specs = specs
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_tag_limits(tag_counts)
|
60
|
+
error_messages = []
|
61
|
+
@tag_expression.limits.each do |tag_name, tag_limit|
|
62
|
+
tag_locations = (tag_counts[tag_name] || [])
|
63
|
+
tag_count = tag_locations.length
|
64
|
+
if tag_count > tag_limit
|
65
|
+
error = "#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
|
66
|
+
tag_locations.join("\n ")
|
67
|
+
error_messages << error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
raise TagExcess.new(error_messages) if error_messages.any?
|
71
|
+
end
|
72
|
+
|
73
|
+
def log
|
74
|
+
Lucid.logger
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'lucid/tdl_builder'
|
2
|
+
require 'gherkin/formatter/filter_formatter'
|
3
|
+
require 'gherkin/formatter/tag_count_formatter'
|
4
|
+
require 'gherkin/parser/parser'
|
5
|
+
|
6
|
+
module Lucid
|
7
|
+
class SpecFile
|
8
|
+
SPEC_PATTERN = /^([\w\W]*?):([\d:]+)$/ #:nodoc:
|
9
|
+
DEFAULT_ENCODING = "UTF-8" #:nodoc:
|
10
|
+
NON_EXEC_PATTERN = /^\s*#|^\s*$/ #:nodoc:
|
11
|
+
ENCODING_PATTERN = /^\s*#\s*encoding\s*:\s*([^\s]+)/ #:nodoc:
|
12
|
+
|
13
|
+
# The +uri+ argument is the location of the source. It can be a path
|
14
|
+
# or a path:line1:line2 etc.
|
15
|
+
def initialize(uri, source=nil)
|
16
|
+
@source = source
|
17
|
+
_, @path, @lines = *SPEC_PATTERN.match(uri)
|
18
|
+
if @path
|
19
|
+
@lines = @lines.split(':').map { |line| line.to_i }
|
20
|
+
else
|
21
|
+
@path = uri
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# The parse action will parse a specific spec source and will return
|
26
|
+
# a the high level construct of the spec. If any filters are passed
|
27
|
+
# in, the result will be filtered accordingly.
|
28
|
+
# @see Lucid::Runtime::SpecsLoader.load
|
29
|
+
def parse(specified_filters, tag_counts)
|
30
|
+
filters = @lines || specified_filters
|
31
|
+
|
32
|
+
tdl_builder = Lucid::Parser::TDLBuilder.new(@path)
|
33
|
+
filter_formatter = filters.empty? ? tdl_builder : Gherkin::Formatter::FilterFormatter.new(tdl_builder, filters)
|
34
|
+
tag_count_formatter = Gherkin::Formatter::TagCountFormatter.new(filter_formatter, tag_counts)
|
35
|
+
|
36
|
+
# Gherkin Parser parameters:
|
37
|
+
# formatter, raise_on_error, machine_name, force_ruby
|
38
|
+
# The machine name refers to a state machine table.
|
39
|
+
parser = Gherkin::Parser::Parser.new(tag_count_formatter, true, "root", false)
|
40
|
+
|
41
|
+
begin
|
42
|
+
# parse parameters:
|
43
|
+
# gherkin, feature_uri, line_offset
|
44
|
+
parser.parse(source, @path, 0)
|
45
|
+
tdl_builder.language = parser.i18n_language
|
46
|
+
tdl_builder.result
|
47
|
+
rescue Gherkin::Lexer::LexingError, Gherkin::Parser::ParseError => e
|
48
|
+
e.message.insert(0, "#{@path}: ")
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# The source method is used to return a properly encoded spec file.
|
54
|
+
# If the spec source read in declares a different encoding, then this
|
55
|
+
# method will make sure to use Lucid's default encoding.
|
56
|
+
def source
|
57
|
+
@source ||= if @path =~ /^http/
|
58
|
+
require 'open-uri'
|
59
|
+
open(@path).read
|
60
|
+
else
|
61
|
+
begin
|
62
|
+
source = File.open(@path, Lucid.file_mode('r', DEFAULT_ENCODING)).read
|
63
|
+
encoding = encoding_for(source)
|
64
|
+
if(DEFAULT_ENCODING.downcase != encoding.downcase)
|
65
|
+
source = File.open(@path, Lucid.file_mode('r', encoding)).read
|
66
|
+
source = to_default_encoding(source, encoding)
|
67
|
+
end
|
68
|
+
source
|
69
|
+
rescue Errno::EACCES => e
|
70
|
+
e.message << "\nLucid was unable to access #{File.expand_path(@path)}."
|
71
|
+
raise e
|
72
|
+
rescue Errno::ENOENT => e
|
73
|
+
if @path == "specs"
|
74
|
+
e.message << ["\nYou don't have a 'specs' directory. This is the default specification",
|
75
|
+
"directory that Lucid will use if one is not specified. So either create",
|
76
|
+
"that directory or specify where your test repository is located."].join("\n")
|
77
|
+
else
|
78
|
+
e.message << ["\nThere is no '#{@path}' directory. Since that is what you specified as",
|
79
|
+
"your spec repository, this directory must be present."].join("\n")
|
80
|
+
end
|
81
|
+
raise e
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def encoding_for(source)
|
89
|
+
encoding = DEFAULT_ENCODING
|
90
|
+
|
91
|
+
source.each_line do |line|
|
92
|
+
break unless NON_EXEC_PATTERN =~ line
|
93
|
+
if ENCODING_PATTERN =~ line
|
94
|
+
encoding = $1
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
encoding
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_default_encoding(string, encoding)
|
103
|
+
if string.respond_to?(:encode)
|
104
|
+
string.encode(DEFAULT_ENCODING)
|
105
|
+
else
|
106
|
+
require 'iconv'
|
107
|
+
Iconv.new(DEFAULT_ENCODING, encoding).iconv(string)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|