teaspoon 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +382 -260
  3. data/app/assets/javascripts/teaspoon-angular.js +108 -26241
  4. data/app/assets/javascripts/teaspoon-jasmine.js +103 -2642
  5. data/app/assets/javascripts/teaspoon-mocha.js +109 -5416
  6. data/app/assets/javascripts/teaspoon-qunit.js +107 -2255
  7. data/app/assets/javascripts/teaspoon-teaspoon.js +0 -1
  8. data/app/assets/javascripts/teaspoon/angular.coffee +3 -1
  9. data/app/assets/javascripts/teaspoon/base/hook.coffee +21 -0
  10. data/app/assets/javascripts/teaspoon/base/reporters/html.coffee +26 -14
  11. data/app/assets/javascripts/teaspoon/base/reporters/html/progress_view.coffee +1 -1
  12. data/app/assets/javascripts/teaspoon/base/reporters/html/template.coffee +3 -3
  13. data/app/assets/javascripts/teaspoon/base/teaspoon.coffee +10 -1
  14. data/app/assets/javascripts/teaspoon/jasmine.coffee +3 -1
  15. data/app/assets/javascripts/teaspoon/mocha.coffee +3 -1
  16. data/app/assets/javascripts/teaspoon/mocha/reporters/html.coffee +1 -1
  17. data/app/assets/javascripts/teaspoon/qunit.coffee +3 -1
  18. data/app/assets/javascripts/teaspoon/qunit/reporters/html.coffee +1 -1
  19. data/app/assets/javascripts/teaspoon/teaspoon.coffee +0 -1
  20. data/app/assets/stylesheets/teaspoon.css +12 -8
  21. data/app/controllers/teaspoon/suite_controller.rb +32 -0
  22. data/app/views/teaspoon/suite/_body.html.erb +0 -0
  23. data/app/views/teaspoon/suite/_boot.html.erb +4 -0
  24. data/app/views/teaspoon/suite/_boot_require_js.html.erb +19 -0
  25. data/app/views/teaspoon/{spec/suites.html.erb → suite/index.html.erb} +6 -7
  26. data/app/views/teaspoon/suite/show.html.erb +19 -0
  27. data/bin/teaspoon +1 -1
  28. data/config/routes.rb +14 -4
  29. data/lib/generators/teaspoon/install/POST_INSTALL +2 -2
  30. data/lib/generators/teaspoon/install/install_generator.rb +22 -11
  31. data/lib/generators/teaspoon/install/templates/_body.html.erb +0 -0
  32. data/lib/generators/teaspoon/install/templates/_boot.html.erb +4 -0
  33. data/lib/generators/teaspoon/install/templates/jasmine/env.rb +11 -0
  34. data/lib/generators/teaspoon/install/templates/jasmine/env_comments.rb +182 -0
  35. data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.coffee +8 -6
  36. data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.js +8 -7
  37. data/lib/generators/teaspoon/install/templates/mocha/env.rb +11 -0
  38. data/lib/generators/teaspoon/install/templates/mocha/env_comments.rb +182 -0
  39. data/lib/generators/teaspoon/install/templates/mocha/spec_helper.coffee +13 -13
  40. data/lib/generators/teaspoon/install/templates/mocha/spec_helper.js +13 -13
  41. data/lib/generators/teaspoon/install/templates/qunit/env.rb +11 -0
  42. data/lib/generators/teaspoon/install/templates/qunit/env_comments.rb +182 -0
  43. data/lib/generators/teaspoon/install/templates/qunit/test_helper.coffee +6 -5
  44. data/lib/generators/teaspoon/install/templates/qunit/test_helper.js +6 -5
  45. data/lib/tasks/teaspoon.rake +9 -2
  46. data/lib/teaspoon.rb +4 -6
  47. data/lib/teaspoon/command_line.rb +116 -134
  48. data/lib/teaspoon/configuration.rb +144 -66
  49. data/lib/teaspoon/console.rb +70 -37
  50. data/lib/teaspoon/coverage.rb +42 -15
  51. data/lib/teaspoon/deprecated.rb +65 -0
  52. data/lib/teaspoon/drivers/base.rb +10 -0
  53. data/lib/teaspoon/drivers/phantomjs/runner.js +9 -11
  54. data/lib/teaspoon/drivers/phantomjs_driver.rb +21 -21
  55. data/lib/teaspoon/drivers/selenium_driver.rb +32 -13
  56. data/lib/teaspoon/engine.rb +32 -12
  57. data/lib/teaspoon/environment.rb +16 -12
  58. data/lib/teaspoon/exceptions.rb +41 -5
  59. data/lib/teaspoon/exporter.rb +52 -0
  60. data/lib/teaspoon/formatters/base.rb +171 -0
  61. data/lib/teaspoon/formatters/clean_formatter.rb +2 -4
  62. data/lib/teaspoon/formatters/documentation_formatter.rb +60 -0
  63. data/lib/teaspoon/formatters/dot_formatter.rb +12 -90
  64. data/lib/teaspoon/formatters/json_formatter.rb +36 -0
  65. data/lib/teaspoon/formatters/junit_formatter.rb +51 -32
  66. data/lib/teaspoon/formatters/modules/report_module.rb +76 -0
  67. data/lib/teaspoon/formatters/pride_formatter.rb +23 -27
  68. data/lib/teaspoon/formatters/snowday_formatter.rb +7 -11
  69. data/lib/teaspoon/formatters/swayze_or_oprah_formatter.rb +88 -64
  70. data/lib/teaspoon/formatters/tap_formatter.rb +18 -27
  71. data/lib/teaspoon/formatters/tap_y_formatter.rb +35 -45
  72. data/lib/teaspoon/formatters/teamcity_formatter.rb +69 -31
  73. data/lib/teaspoon/instrumentation.rb +33 -33
  74. data/lib/teaspoon/result.rb +2 -1
  75. data/lib/teaspoon/runner.rb +40 -28
  76. data/lib/teaspoon/server.rb +23 -25
  77. data/lib/teaspoon/suite.rb +52 -72
  78. data/lib/teaspoon/utility.rb +3 -14
  79. data/lib/teaspoon/version.rb +1 -1
  80. data/spec/dummy/app/assets/javascripts/integration/integration_spec.coffee +3 -0
  81. data/spec/dummy/app/assets/javascripts/integration/spec_helper.coffee +2 -0
  82. data/spec/dummy/config/application.rb +3 -0
  83. data/spec/features/console_reporter_spec.rb +48 -18
  84. data/spec/features/hooks_spec.rb +23 -41
  85. data/spec/features/html_reporter_spec.rb +38 -21
  86. data/spec/features/install_generator_spec.rb +34 -20
  87. data/spec/features/instrumentation_spec.rb +3 -2
  88. data/spec/fixtures/coverage.json +243 -0
  89. data/spec/javascripts/fixtures/_body.html.erb +1 -0
  90. data/spec/javascripts/jasmine_helper.coffee +1 -1
  91. data/spec/javascripts/teaspoon/base/fixture_spec.coffee +4 -4
  92. data/spec/javascripts/teaspoon/base/reporters/html_spec.coffee +9 -10
  93. data/spec/javascripts/teaspoon/mocha/reporters/html_mspec.coffee +0 -6
  94. data/spec/javascripts/teaspoon/phantomjs/runner_spec.coffee +5 -6
  95. data/spec/javascripts/turbolinks_helper.coffee +1 -1
  96. data/spec/spec_helper.rb +3 -4
  97. data/spec/teaspoon/command_line_spec.rb +139 -23
  98. data/spec/teaspoon/configuration_spec.rb +164 -46
  99. data/spec/teaspoon/console_spec.rb +142 -47
  100. data/spec/teaspoon/coverage_spec.rb +98 -28
  101. data/spec/teaspoon/drivers/base_spec.rb +5 -0
  102. data/spec/teaspoon/drivers/phantomjs_driver_spec.rb +32 -14
  103. data/spec/teaspoon/drivers/selenium_driver_spec.rb +32 -24
  104. data/spec/teaspoon/engine_spec.rb +8 -5
  105. data/spec/teaspoon/environment_spec.rb +56 -33
  106. data/spec/teaspoon/exceptions_spec.rb +57 -0
  107. data/spec/teaspoon/exporter_spec.rb +96 -0
  108. data/spec/teaspoon/formatters/base_spec.rb +259 -0
  109. data/spec/teaspoon/formatters/clean_formatter_spec.rb +37 -0
  110. data/spec/teaspoon/formatters/documentation_formatter_spec.rb +127 -0
  111. data/spec/teaspoon/formatters/dot_formatter_spec.rb +52 -56
  112. data/spec/teaspoon/formatters/json_formatter_spec.rb +77 -0
  113. data/spec/teaspoon/formatters/junit_formatter_spec.rb +72 -35
  114. data/spec/teaspoon/formatters/pride_formatter_spec.rb +37 -0
  115. data/spec/teaspoon/formatters/snowday_formatter_spec.rb +35 -0
  116. data/spec/teaspoon/formatters/tap_formatter_spec.rb +29 -81
  117. data/spec/teaspoon/formatters/tap_y_formatter_spec.rb +31 -141
  118. data/spec/teaspoon/formatters/teamcity_formatter_spec.rb +99 -42
  119. data/spec/teaspoon/instrumentation_spec.rb +44 -44
  120. data/spec/teaspoon/result_spec.rb +37 -0
  121. data/spec/teaspoon/runner_spec.rb +70 -59
  122. data/spec/teaspoon/server_spec.rb +34 -52
  123. data/spec/teaspoon/suite_spec.rb +42 -188
  124. data/spec/teaspoon_env.rb +39 -28
  125. data/vendor/assets/javascripts/{angular-scenario-1.0.5.js → angular/1.0.5.js} +0 -0
  126. data/vendor/assets/javascripts/{angular-scenario-1.0.5.MIT-LICENSE → angular/MIT-LICENSE} +0 -0
  127. data/vendor/assets/javascripts/{jasmine-1.3.1.js → jasmine/1.3.1.js} +0 -0
  128. data/vendor/assets/javascripts/jasmine/2.0.0.js +2412 -0
  129. data/vendor/assets/javascripts/{jasmine-1.3.1.MIT.LICENSE → jasmine/MIT.LICENSE} +0 -0
  130. data/vendor/assets/javascripts/{mocha-1.10.0.js → mocha/1.10.0.js} +1 -0
  131. data/vendor/assets/javascripts/mocha/1.17.1.js +5813 -0
  132. data/vendor/assets/javascripts/{mocha-1.10.1.MIT.LICENSE → mocha/MIT.LICENSE} +0 -0
  133. data/vendor/assets/javascripts/{qunit-1.12.0.js → qunit/1.12.0.js} +1 -1
  134. data/vendor/assets/javascripts/qunit/1.14.0.js +2288 -0
  135. data/vendor/assets/javascripts/{qunit-1.12.0.MIT.LICENSE → qunit/MIT.LICENSE} +0 -0
  136. data/vendor/assets/javascripts/support/chai.js +827 -385
  137. data/vendor/assets/javascripts/support/jasmine-jquery-1.7.0.js +720 -0
  138. data/vendor/assets/javascripts/support/jasmine-jquery-2.0.0.js +812 -0
  139. data/vendor/assets/javascripts/support/sinon-chai.js +17 -0
  140. data/vendor/assets/javascripts/support/sinon.js +1138 -643
  141. metadata +57 -36
  142. data/app/controllers/teaspoon/spec_controller.rb +0 -38
  143. data/app/helpers/teaspoon/spec_helper.rb +0 -36
  144. data/app/views/teaspoon/spec/_require_js.html.erb +0 -21
  145. data/app/views/teaspoon/spec/_standard.html.erb +0 -4
  146. data/app/views/teaspoon/spec/runner.html.erb +0 -19
  147. data/lib/generators/teaspoon/install/templates/env.rb +0 -38
  148. data/lib/generators/teaspoon/install/templates/jasmine/initializer.rb +0 -64
  149. data/lib/generators/teaspoon/install/templates/mocha/initializer.rb +0 -64
  150. data/lib/generators/teaspoon/install/templates/qunit/initializer.rb +0 -64
  151. data/lib/teaspoon/check_coverage.rb +0 -33
  152. data/lib/teaspoon/drivers/base_driver.rb +0 -10
  153. data/lib/teaspoon/exception_handling.rb +0 -18
  154. data/lib/teaspoon/formatters/base_formatter.rb +0 -63
  155. data/spec/dummy/config/initializers/teaspoon.rb +0 -41
  156. data/spec/teaspoon/check_coverage_spec.rb +0 -50
  157. data/spec/teaspoon/formatters/base_formatter_spec.rb +0 -45
  158. data/vendor/assets/javascripts/support/chai.MIT.LICENSE +0 -22
  159. data/vendor/assets/javascripts/support/expect.MIT.LICENSE +0 -22
  160. data/vendor/assets/javascripts/support/jasmine-jquery.MIT.LICENSE +0 -20
  161. data/vendor/assets/javascripts/support/jasmine-jquery.js +0 -659
  162. data/vendor/assets/javascripts/support/sinon-chai.MIT-ISH.LICENSE +0 -13
  163. data/vendor/assets/javascripts/support/sinon.BSD.LICENSE +0 -27
@@ -1,56 +1,94 @@
1
- require 'teaspoon/formatters/base_formatter'
2
-
3
1
  module Teaspoon
4
2
  module Formatters
5
- class TeamcityFormatter < BaseFormatter
6
- def runner(result)
7
- log "##teamcity[testSuiteStarted name='Jasmine']"
3
+ class TeamcityFormatter < Base
4
+
5
+ def initialize(*args)
6
+ log("enteredTheMatrix timestamp='#{Time.now.to_json.gsub('"', "")}'")
7
+ super
8
+ end
9
+
10
+ protected
11
+
12
+ def log_runner(result)
13
+ log("testCount count='#{result.total}' timestamp='#{result.start}'")
8
14
  end
9
15
 
10
- def spec(result)
11
- log "##teamcity[testStarted name='#{escape result.description}' captureStandardOutput='false']"
16
+ def log_suite(result)
17
+ log_end_suite
18
+ log("testSuiteStarted name='#{result.label}'")
19
+ end
20
+
21
+ def log_passing_spec(result)
22
+ log_teamcity_spec(type: "testStarted", desc: escape(result.description))
23
+ end
24
+
25
+ def log_pending_spec(result)
26
+ log_teamcity_spec(type: "testIgnored", desc: escape(result.description))
27
+ end
12
28
 
13
- unless result.passing? || result.pending?
14
- log "##teamcity[testFailed name='#{escape result.description}' message='#{escape result.message}']"
29
+ def log_failing_spec(result)
30
+ log_teamcity_spec(type: "testStarted", desc: escape(result.description)) do
31
+ log("testFailed name='#{escape(result.description)}' message='#{escape(result.message)}'")
15
32
  end
33
+ end
16
34
 
17
- log "##teamcity[testFinished name='#{escape result.description}']"
35
+ def log_error(result)
36
+ log("message text='#{escape(result.message)}' errorDetails='#{escape_trace(result.trace)}' status='ERROR'")
18
37
  end
19
38
 
20
- def error(error)
21
- log "##teamcity[message text='#{escape error.message}' errorDetails='#{escape format_trace(error.trace)}' status='ERROR']"
39
+ def log_result(result)
40
+ log_end_suite
41
+ @result = result
22
42
  end
23
43
 
24
- def result(result)
25
- log "##teamcity[testSuiteFinished name='Jasmine']"
44
+ def log_coverage(message)
45
+ log("testSuiteStarted name='Coverage summary'")
46
+ log_line(message)
47
+ log("testSuiteFinished name='Coverage summary'")
26
48
  end
27
49
 
28
- def suppress_logs?
29
- true
50
+ def log_threshold_failure(message)
51
+ log("testSuiteStarted name='Coverage thresholds'")
52
+ log_teamcity_spec(type: "testStarted", desc: "Coverage thresholds") do
53
+ log("testFailed name='Coverage thresholds' message='were not met'")
54
+ log_line(message)
55
+ end
56
+ log("testSuiteFinished name='Coverage thresholds'")
57
+ end
58
+
59
+ def log_complete(failure_count)
60
+ log_line("\nFinished in #{@result.elapsed} seconds")
61
+ stats = "#{pluralize("example", run_count)}, #{pluralize("failure", failure_count)}"
62
+ stats << ", #{pendings.size} pending" if pendings.size > 0
63
+ log_line(stats)
64
+ log_line if failure_count > 0
30
65
  end
31
66
 
32
67
  private
68
+
69
+ def log_end_suite
70
+ log("testSuiteFinished name='#{escape(@last_suite.label)}'") if @last_suite
71
+ end
72
+
73
+ def log_teamcity_spec(opts, &block)
74
+ log("#{opts[:type]} name='#{opts[:desc]}' captureStandardOutput='true'")
75
+ log_line(@stdout.gsub(/\n$/, "")) unless @stdout.blank?
76
+ yield if block_given?
77
+ log("testFinished name='#{opts[:desc]}'")
78
+ end
79
+
33
80
  def log(str)
34
- STDOUT.print("#{str}\n")
81
+ log_line("##teamcity[#{str}]")
35
82
  end
36
83
 
37
84
  def escape(str)
38
- {
39
- /\|/m => "||",
40
- /'/m => "|'",
41
- /\n/m => "|n",
42
- /\r/m => "|r",
43
- /\[/m => "|[",
44
- /\]/m => "|]",
45
- }.inject(str) do |result, (regex, sub)|
46
- result.gsub(regex, sub)
47
- end
85
+ str = str.gsub(/[|'\[\]]/) { |c| "|#{c}" }
86
+ str.gsub("\n", "|n").gsub("\r", "|r")
48
87
  end
49
88
 
50
- def format_trace(trace)
51
- trace.map { |line|
52
- ["#{line['file']}:#{line['line']}", line['function']].compact.join(' ')
53
- }.join("\n")
89
+ def escape_trace(trace)
90
+ lines = trace.map { |t| ["#{t["file"]}:#{t["line"]}", t["function"]].compact.join(" ") }
91
+ escape(lines.join("\n"))
54
92
  end
55
93
  end
56
94
  end
@@ -4,56 +4,56 @@ module Teaspoon
4
4
  class Instrumentation
5
5
  extend Teaspoon::Utility
6
6
 
7
- def self.executable
8
- @executable ||= istanbul()
7
+ def self.add_to(response, env)
8
+ return response unless add?(response, env)
9
+ Teaspoon::Instrumentation.new(response).instrumented_response
9
10
  end
10
11
 
11
12
  def self.add?(response, env)
12
- (
13
- executable.present? && # we have an executable
14
- env["QUERY_STRING"].to_s =~ /instrument=(1|true)/ && # the instrument param was provided
15
- response[0] == 200 && # the status is 200
16
- response[1]["Content-Type"].to_s == "application/javascript" && # the format is something that we care about
17
- response[2].respond_to?(:source) # it looks like an asset
18
- )
13
+ executable && # we have an executable
14
+ env["QUERY_STRING"].to_s =~ /instrument=(1|true)/ && # the instrument param was provided
15
+ response[0] == 200 && # the status is 200 (304 might be needed here too)
16
+ response[1]["Content-Type"].to_s == "application/javascript" && # the format is something that we care about
17
+ response[2].respond_to?(:source) # it looks like an asset
19
18
  end
20
19
 
21
- def self.add_to(response, env)
22
- return response unless add?(response, env)
23
- Teaspoon::Instrumentation.new(response)
24
- response
20
+ def self.executable
21
+ return @executable if @executable_checked
22
+ @executable_checked = true
23
+ @executable = which("istanbul")
25
24
  end
26
25
 
27
26
  def initialize(response)
28
- status, headers, @asset = response
29
- headers, @asset = [headers.clone, @asset.clone]
30
- result = process_and_instrument
31
- length = result.bytesize.to_s
27
+ @response = response
28
+ end
32
29
 
33
- headers["Content-Length"] = length
34
- @asset.instance_variable_set(:@source, result)
35
- @asset.instance_variable_set(:@length, length)
30
+ def instrumented_response
31
+ status, headers, asset = @response
32
+ headers, asset = [headers.clone, asset.clone]
36
33
 
37
- response.replace([status, headers, @asset])
38
- end
34
+ result = add_instrumentation(asset)
39
35
 
40
- private
36
+ asset.instance_variable_set(:@source, result)
37
+ asset.instance_variable_set(:@length, headers["Content-Length"] = result.bytesize.to_s)
38
+
39
+ [status, headers, asset]
40
+ end
41
41
 
42
- def process_and_instrument
43
- file = @asset.pathname.to_s
44
- Dir.mktmpdir do |path|
45
- filename = File.basename(file)
46
- input = File.join(path, filename).sub(/\.js.+/, ".js")
47
- File.open(input, 'w') { |file| file.write(@asset.source) }
42
+ protected
48
43
 
49
- instrument(input).gsub(input, file)
44
+ def add_instrumentation(asset)
45
+ source_path = asset.pathname.to_s
46
+ Dir.mktmpdir do |temp_path|
47
+ input_path = File.join(temp_path, File.basename(source_path)).sub(/\.js.+/, ".js")
48
+ File.open(input_path, 'w') { |f| f.write(asset.source) }
49
+ instrument(input_path).gsub(input_path, source_path)
50
50
  end
51
51
  end
52
52
 
53
53
  def instrument(input)
54
- result = %x{#{Teaspoon::Instrumentation.executable} instrument --embed-source #{input.shellescape}}
55
- raise "Could not generate instrumentation for #{File.basename(input)}" unless $?.exitstatus == 0
56
- result
54
+ result = %x{#{self.class.executable} instrument --embed-source #{input.shellescape}}
55
+ return result if $?.exitstatus == 0
56
+ raise Teaspoon::DependencyFailure, "Could not generate instrumentation for #{File.basename(input)}"
57
57
  end
58
58
  end
59
59
 
@@ -13,7 +13,8 @@ module Teaspoon
13
13
  :total,
14
14
  :start,
15
15
  :level,
16
- :coverage
16
+ :coverage,
17
+ :original_json,
17
18
  ]
18
19
 
19
20
  class Result < Struct.new(*RESULT_ATTRS)
@@ -4,56 +4,68 @@ require "teaspoon/result"
4
4
  module Teaspoon
5
5
  class Runner
6
6
 
7
- attr_accessor :formatters
8
- attr_reader :failure_count
7
+ attr_reader :failure_count
9
8
 
10
9
  def initialize(suite_name = :default)
11
10
  @suite_name = suite_name
12
- @formatters = Teaspoon.configuration.formatters.map{ |f| resolve_formatter(f).new(suite_name) }
13
11
  @failure_count = 0
14
- end
15
-
16
- def suppress_logs?
17
- return @suppress_logs unless @suppress_logs.nil?
18
- @suppress_logs = Teaspoon.configuration.suppress_log
19
- return true if @suppress_logs
20
- for formatter in @formatters
21
- return @suppress_logs = true if formatter.suppress_logs?
22
- end
23
- @suppress_logs = false
12
+ @formatters = Teaspoon.configuration.formatters.map{ |f| resolve_formatter(f) }
24
13
  end
25
14
 
26
15
  def process(line)
27
- return if output_from(line)
28
- log line unless suppress_logs?
16
+ if result = result_from_line(line)
17
+ return notify_formatters(result.type, result)
18
+ end
19
+ notify_formatters("console", line) unless Teaspoon.configuration.suppress_log
29
20
  end
30
21
 
31
22
  private
32
23
 
33
24
  def resolve_formatter(formatter)
34
- Teaspoon::Formatters.const_get("#{formatter.to_s.camelize}Formatter")
25
+ formatter, output = formatter.to_s.split(">")
26
+ formatter = Teaspoon::Formatters.const_get("#{formatter.camelize}Formatter")
27
+ formatter.new(@suite_name, output)
28
+ rescue NameError
29
+ raise Teaspoon::UnknownFormatter, "Unknown formatter: \"#{formatter}\""
35
30
  end
36
31
 
37
- def output_from(line)
32
+ def notify_formatters(event, result)
33
+ @formatters.each { |f| f.send(event, result) if f.respond_to?(event) }
34
+ send(:"on_#{event}", result) if respond_to?(:"on_#{event}", true)
35
+ end
36
+
37
+ def result_from_line(line)
38
38
  json = JSON.parse(line)
39
39
  return false unless json && json["_teaspoon"] && json["type"]
40
- result = Teaspoon::Result.build_from_json(json)
41
- notify_formatters result
42
- @failure_count += 1 if result.failing?
43
- return true
40
+ json["original_json"] = line
41
+ return result_from_json(json)
44
42
  rescue JSON::ParserError
45
43
  false
46
44
  end
47
45
 
48
- def notify_formatters(result)
49
- @formatters.each do |formatter|
50
- event = result.type
51
- formatter.send(event, result) if formatter.respond_to?(event)
52
- end
46
+ def result_from_json(json)
47
+ result = Teaspoon::Result.build_from_json(json)
48
+ @failure_count += 1 if result.failing?
49
+ result
50
+ end
51
+
52
+ def on_exception(result)
53
+ raise Teaspoon::RunnerException, result.message
54
+ end
55
+
56
+ def on_result(result)
57
+ resolve_coverage(result.coverage)
58
+ notify_formatters("complete", @failure_count)
53
59
  end
54
60
 
55
- def log(msg)
56
- STDOUT.print msg
61
+ def resolve_coverage(data)
62
+ return unless Teaspoon.configuration.use_coverage && data.present?
63
+ coverage = Teaspoon::Coverage.new(@suite_name, Teaspoon.configuration.use_coverage, data)
64
+ coverage.generate_reports { |msg| notify_formatters("coverage", msg) }
65
+ coverage.check_thresholds do |msg|
66
+ notify_formatters("threshold_failure", msg)
67
+ @failure_count += 1
68
+ end
57
69
  end
58
70
  end
59
71
  end
@@ -5,51 +5,50 @@ require "webrick"
5
5
  module Teaspoon
6
6
  class Server
7
7
 
8
+ attr_accessor :port
9
+
8
10
  def initialize
9
- @port = find_available_port
10
- if defined?(Thin)
11
- if Teaspoon.configuration.suppress_log
12
- Thin::Logging.silent = true
13
- else
14
- Thin::Logging.trace = false
15
- end
16
- end
11
+ @port = Teaspoon.configuration.server_port || find_available_port
17
12
  end
18
13
 
19
14
  def start
20
- STDOUT.print "Starting the Teaspoon server...\n" unless Teaspoon.configuration.suppress_log
21
- @thread = Thread.new do
15
+ thread = Thread.new do
16
+ disable_logging
22
17
  server = Rack::Server.new(rack_options)
23
18
  server.start
24
19
  end
25
- wait_until_started
20
+ wait_until_started(thread)
26
21
  rescue => e
27
- raise "Cannot start server: #{e.message}"
28
- end
29
-
30
- def wait_until_started
31
- Timeout.timeout(Teaspoon.configuration.server_timeout.to_i) { @thread.join(0.1) until responsive? }
32
- rescue Timeout::Error
33
- raise "Server failed to start. You may need to increase the timeout configuration."
22
+ raise Teaspoon::ServerException, "Cannot start server: #{e.message}"
34
23
  end
35
24
 
36
25
  def responsive?
37
- return false if @thread && @thread.join(0)
38
26
  TCPSocket.new("127.0.0.1", port).close
39
- return true
27
+ true
40
28
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
41
- return false
29
+ false
42
30
  end
43
31
 
44
32
  def url
45
33
  "http://127.0.0.1:#{port}"
46
34
  end
47
35
 
48
- def port
49
- @port
36
+ protected
37
+
38
+ def wait_until_started(thread)
39
+ Timeout.timeout(Teaspoon.configuration.server_timeout.to_i) { thread.join(0.1) until responsive? }
40
+ rescue Timeout::Error
41
+ raise Teaspoon::ServerException, "Server failed to start. You may need to increase the timeout configuration."
50
42
  end
51
43
 
52
- protected
44
+ def disable_logging
45
+ return unless defined?(Thin)
46
+ if Teaspoon.configuration.suppress_log
47
+ Thin::Logging.silent = true
48
+ else
49
+ Thin::Logging.trace = false
50
+ end
51
+ end
53
52
 
54
53
  def rack_options
55
54
  {
@@ -63,7 +62,6 @@ module Teaspoon
63
62
  end
64
63
 
65
64
  def find_available_port
66
- return Teaspoon.configuration.server_port if Teaspoon.configuration.server_port
67
65
  server = TCPServer.new("127.0.0.1", 0)
68
66
  server.addr[1]
69
67
  ensure
@@ -1,78 +1,79 @@
1
1
  module Teaspoon
2
2
  class Suite
3
3
 
4
- attr_accessor :config, :name
5
-
6
4
  def self.all
7
- Teaspoon.configuration.suites.keys.map { |suite| Teaspoon::Suite.new(suite: suite) }
5
+ @all ||= Teaspoon.configuration.suite_configs.keys.map { |suite| Teaspoon::Suite.new(suite: suite) }
8
6
  end
9
7
 
10
8
  def self.resolve_spec_for(file)
11
- suites = all
12
- suites.each do |suite|
9
+ all.each do |suite|
13
10
  spec = suite.include_spec_for?(file)
14
11
  return {suite: suite.name, path: spec} if spec
15
12
  end
16
13
  false
17
14
  end
18
15
 
16
+ attr_accessor :config, :name
17
+ delegate :helper, :stylesheets, :javascripts, :boot_partial, :body_partial, :no_coverage, :hooks,
18
+ to: :config
19
+
19
20
  def initialize(options = {})
20
21
  @options = options
21
22
  @name = (@options[:suite] || :default).to_s
22
23
  @config = suite_configuration
24
+ @env = Rails.application.assets
23
25
  end
24
26
 
25
- def stylesheets
26
- config.stylesheets
27
- end
28
-
29
- def helper
30
- config.helper
31
- end
32
-
33
- def javascripts
34
- [core_javascripts, spec_javascripts].flatten
35
- end
36
-
37
- def core_javascripts
38
- config.javascripts
27
+ def spec_files
28
+ glob.map { |file| {path: file, name: asset_from_file(file)} }
39
29
  end
40
30
 
41
- def js_config
42
- config.js_config
31
+ def spec_assets(include_helper = true)
32
+ assets = specs
33
+ assets.unshift(helper) if include_helper && helper
34
+ asset_tree(assets)
43
35
  end
44
36
 
45
- def boot_partial
46
- config.boot_partial
37
+ def include_spec?(file)
38
+ glob.include?(file)
47
39
  end
48
40
 
49
- def spec_javascripts
50
- [helper, specs].flatten
41
+ def include_spec_for?(file)
42
+ return file if glob.include?(file)
43
+ paths = glob.select { |path| path.include?(file) }
44
+ return paths unless paths.empty?
45
+ false
51
46
  end
52
47
 
53
- def spec_javascripts_for_require
54
- specs.map { |path|
55
- file_without_ext = path.split('.').first
56
- "#{file_without_ext}"
57
- }
58
- end
48
+ protected
59
49
 
60
- def suites
61
- {all: Teaspoon.configuration.suites.keys, active: name}
50
+ def specs
51
+ files = specs_from_file
52
+ return files unless files.empty?
53
+ glob.map { |file| asset_from_file(file) }
62
54
  end
63
55
 
64
- def spec_files
65
- glob.map { |file| {path: file, name: asset_from_file(file)} }
56
+ def asset_tree(sources)
57
+ sources.collect do |source|
58
+ asset = @env.find_asset(source)
59
+ if asset && asset.respond_to?(:logical_path)
60
+ asset.to_a.map { |a| asset_url(a) }
61
+ else
62
+ source unless source.blank?
63
+ end
64
+ end.flatten.compact.uniq
66
65
  end
67
66
 
68
- def link(params = {})
69
- query = "/?#{params.to_query}" if params.present?
70
- "#{Teaspoon.configuration.context}#{Teaspoon.configuration.mount_at}/#{name}#{query}"
67
+ def asset_url(asset)
68
+ params = "?body=1"
69
+ params << "&instrument=1" if instrument_file?(asset.pathname.to_s)
70
+ "#{asset.logical_path}#{params}"
71
71
  end
72
72
 
73
73
  def instrument_file?(file)
74
+ return false unless @options[:coverage] || Teaspoon.configuration.use_coverage
74
75
  return false if include_spec?(file)
75
- for ignored in @config.no_coverage
76
+ for ignored in no_coverage
76
77
  if ignored.is_a?(String)
77
78
  return false if File.basename(file) == ignored
78
79
  elsif ignored.is_a?(Regexp)
@@ -82,29 +83,18 @@ module Teaspoon
82
83
  true
83
84
  end
84
85
 
85
- def include_spec?(file)
86
- glob.include?(file)
87
- end
88
-
89
- def include_spec_for?(file)
90
- return file if glob.include?(file)
91
- paths = glob.select { |path| path.include?(file) }
92
- return paths unless paths.empty?
93
- false
94
- end
95
-
96
- def run_hooks(group = :default)
97
- config.hooks[group.to_s].each do |hook|
98
- hook.call
86
+ def asset_from_file(original)
87
+ filename = original
88
+ Rails.application.config.assets.paths.each do |path|
89
+ filename = filename.gsub(%r(^#{Regexp.escape(path.to_s)}[\/|\\]), "")
99
90
  end
100
- end
101
91
 
102
- protected
92
+ raise Teaspoon::AssetNotServable, "#{filename} is not within an asset path" if filename == original
93
+ normalize_js_extension(filename)
94
+ end
103
95
 
104
- def specs
105
- files = specs_from_file
106
- return files unless files.empty?
107
- glob.map { |file| asset_from_file(file) }
96
+ def normalize_js_extension(filename)
97
+ filename.gsub('.erb', '').gsub(/(\.js\.coffee|\.coffee)$/, ".js")
108
98
  end
109
99
 
110
100
  def glob
@@ -112,9 +102,9 @@ module Teaspoon
112
102
  end
113
103
 
114
104
  def suite_configuration
115
- config = Teaspoon.configuration.suites[name]
116
- raise Teaspoon::UnknownSuite unless config.present?
117
- Teaspoon::Configuration::Suite.new(&config)
105
+ config = Teaspoon.configuration.suite_configs[name]
106
+ raise Teaspoon::UnknownSuite, "Unknown suite \"#{name}\"" unless config.present?
107
+ config[:instance] ||= Teaspoon::Configuration::Suite.new(&config[:block])
118
108
  end
119
109
 
120
110
  def specs_from_file
@@ -122,15 +112,5 @@ module Teaspoon
122
112
  asset_from_file(File.expand_path(Teaspoon.configuration.root.join(filename)))
123
113
  end
124
114
  end
125
-
126
- def asset_from_file(original)
127
- filename = original
128
- Rails.application.config.assets.paths.each do |path|
129
- path = path.to_s
130
- filename = filename.gsub(%r(^#{Regexp.escape(path)}[\/|\\]), "")
131
- end
132
- raise Teaspoon::AssetNotServable, "#{filename} is not within an asset path" if filename == original
133
- filename.gsub('.erb', '').gsub(/(\.js\.coffee|\.coffee)$/, ".js")
134
- end
135
115
  end
136
116
  end