You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

console.rb 8.8 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. # frozen_string_literal: true
  2. # rubocop:disable Metrics/ModuleLength
  3. require 'cucumber/formatter/ansicolor'
  4. require 'cucumber/formatter/duration'
  5. require 'cucumber/gherkin/i18n'
  6. module Cucumber
  7. module Formatter
  8. # This module contains helper methods that are used by formatters that
  9. # print output to the terminal.
  10. #
  11. # FORMAT is a hash of Proc objects, keyed by step-definition types, e.g.
  12. # "FORMAT[:passed]". The Proc is called for each line of the step's
  13. # output.
  14. #
  15. # format_step calls format_string, format_string calls format_for to obtain
  16. # the formatting Proc.
  17. #
  18. # Example:
  19. #
  20. # The ANSI color console formatter defines a map of step-type to output
  21. # color (e.g. "passed" to "green"), then builds methods named for the
  22. # step-types (e.g. "def passed"), which themselves wrap the corresponding
  23. # color-named methods provided by Term::ANSIColor (e.g. "def red").
  24. #
  25. # During output, each line is processed by passing it to the formatter Proc
  26. # which returns the formatted (e.g. colored) string.
  27. module Console
  28. extend ANSIColor
  29. include Duration
  30. def format_step(keyword, step_match, status, source_indent)
  31. comment = if source_indent
  32. c = indent("# #{step_match.location}", source_indent)
  33. format_string(c, :comment)
  34. else
  35. ''
  36. end
  37. format = format_for(status, :param)
  38. line = keyword + step_match.format_args(format) + comment
  39. format_string(line, status)
  40. end
  41. def format_string(o, status)
  42. fmt = format_for(status)
  43. o.to_s.split("\n").map do |line|
  44. if Proc == fmt.class
  45. fmt.call(line)
  46. else
  47. fmt % line
  48. end
  49. end.join("\n")
  50. end
  51. def print_elements(elements, status, kind)
  52. return if elements.empty?
  53. element_messages = element_messages(elements, status)
  54. print_element_messages(element_messages, status, kind)
  55. end
  56. def print_element_messages(element_messages, status, kind)
  57. if element_messages.any?
  58. @io.puts(format_string("(::) #{status} #{kind} (::)", status))
  59. @io.puts
  60. @io.flush
  61. end
  62. element_messages.each do |message|
  63. @io.puts(format_string(message, status))
  64. @io.puts
  65. @io.flush
  66. end
  67. end
  68. def print_statistics(duration, config, counts, issues)
  69. if issues.any?
  70. @io.puts issues.to_s
  71. @io.puts
  72. end
  73. @io.puts counts.to_s
  74. @io.puts(format_duration(duration)) if duration && config.duration?
  75. if config.randomize?
  76. @io.puts
  77. @io.puts "Randomized with seed #{config.seed}"
  78. end
  79. @io.flush
  80. end
  81. def print_exception(e, status, indent)
  82. string = exception_message_string(e, indent)
  83. @io.puts(format_string(string, status))
  84. end
  85. def exception_message_string(e, indent_amount)
  86. message = "#{e.message} (#{e.class})".dup.force_encoding('UTF-8')
  87. message = linebreaks(message, ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
  88. indent("#{message}\n#{e.backtrace.join("\n")}", indent_amount)
  89. end
  90. # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/10655
  91. def linebreaks(msg, max)
  92. return msg unless max && max > 0
  93. msg.gsub(/.{1,#{max}}(?:\s|\Z)/) do
  94. (Regexp.last_match(0) + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n")
  95. end.rstrip
  96. end
  97. def collect_snippet_data(test_step, ast_lookup)
  98. # collect snippet data for undefined steps
  99. keyword = ast_lookup.snippet_step_keyword(test_step)
  100. @snippets_input << Console::SnippetData.new(keyword, test_step)
  101. end
  102. def collect_undefined_parameter_type_names(undefined_parameter_type)
  103. @undefined_parameter_types << undefined_parameter_type.type_name
  104. end
  105. def print_snippets(options)
  106. return unless options[:snippets]
  107. snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg|
  108. snippet_text(step_keyword, step_name, multiline_arg)
  109. end
  110. do_print_snippets(snippet_text_proc) unless @snippets_input.empty?
  111. @undefined_parameter_types.map do |type_name|
  112. do_print_undefined_parameter_type_snippet(type_name)
  113. end
  114. end
  115. def do_print_snippets(snippet_text_proc)
  116. snippets = @snippets_input.map do |data|
  117. snippet_text_proc.call(data.actual_keyword, data.step.text, data.step.multiline_arg)
  118. end.uniq
  119. text = "\nYou can implement step definitions for undefined steps with these snippets:\n\n"
  120. text += snippets.join("\n\n")
  121. @io.puts format_string(text, :undefined)
  122. @io.puts
  123. @io.flush
  124. end
  125. def print_passing_wip(config, passed_test_cases, ast_lookup)
  126. return unless config.wip?
  127. messages = passed_test_cases.map do |test_case|
  128. scenario_source = ast_lookup.scenario_source(test_case)
  129. keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
  130. linebreaks("#{test_case.location.on_line(test_case.location.lines.max)}:in `#{keyword}: #{test_case.name}'", ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
  131. end
  132. do_print_passing_wip(messages)
  133. end
  134. def do_print_passing_wip(passed_messages)
  135. if passed_messages.any?
  136. @io.puts format_string("\nThe --wip switch was used, so I didn't expect anything to pass. These scenarios passed:", :failed)
  137. print_element_messages(passed_messages, :passed, 'scenarios')
  138. else
  139. @io.puts format_string("\nThe --wip switch was used, so the failures were expected. All is good.\n", :passed)
  140. end
  141. end
  142. def attach(src, media_type)
  143. return unless media_type == 'text/x.cucumber.log+plain'
  144. return unless @io
  145. @io.puts
  146. @io.puts(format_string(src, :tag))
  147. @io.flush
  148. end
  149. def print_profile_information
  150. return if @options[:skip_profile_information] || @options[:profiles].nil? || @options[:profiles].empty?
  151. do_print_profile_information(@options[:profiles])
  152. end
  153. def do_print_profile_information(profiles)
  154. profiles_sentence = if profiles.size == 1
  155. profiles.first
  156. else
  157. "#{profiles[0...-1].join(', ')} and #{profiles.last}"
  158. end
  159. @io.puts "Using the #{profiles_sentence} profile#{'s' if profiles.size > 1}..."
  160. end
  161. def do_print_undefined_parameter_type_snippet(type_name)
  162. camelized = type_name.split(/_|-/).collect(&:capitalize).join
  163. @io.puts [
  164. "The parameter #{type_name} is not defined. You can define a new one with:",
  165. '',
  166. 'ParameterType(',
  167. " name: '#{type_name}',",
  168. ' regexp: /some regexp here/,',
  169. " type: #{camelized},",
  170. ' # The transformer takes as many arguments as there are capture groups in the regexp,',
  171. ' # or just one if there are none.',
  172. " transformer: ->(s) { #{camelized}.new(s) }",
  173. ')',
  174. ''
  175. ].join("\n")
  176. end
  177. def indent(string, padding)
  178. if padding >= 0
  179. string.gsub(/^/, ' ' * padding)
  180. else
  181. string.gsub(/^ {0,#{-padding}}/, '')
  182. end
  183. end
  184. private
  185. FORMATS = Hash.new { |hash, format| hash[format] = method(format).to_proc }
  186. def format_for(*keys)
  187. key = keys.join('_').to_sym
  188. fmt = FORMATS[key]
  189. raise "No format for #{key.inspect}: #{FORMATS.inspect}" if fmt.nil?
  190. fmt
  191. end
  192. def element_messages(elements, status)
  193. elements.map do |element|
  194. if status == :failed
  195. exception_message_string(element.exception, 0)
  196. else
  197. linebreaks(element.backtrace_line, ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
  198. end
  199. end
  200. end
  201. def snippet_text(step_keyword, step_name, multiline_arg)
  202. keyword = Cucumber::Gherkin::I18n.code_keyword_for(step_keyword).strip
  203. config.snippet_generators.map do |generator|
  204. generator.call(keyword, step_name, multiline_arg, config.snippet_type)
  205. end.join("\n")
  206. end
  207. class SnippetData
  208. attr_reader :actual_keyword, :step
  209. def initialize(actual_keyword, step)
  210. @actual_keyword = actual_keyword
  211. @step = step
  212. end
  213. end
  214. end
  215. end
  216. end
  217. # rubocop:enable Metrics/ModuleLength

No Description

Contributors (1)