teaspoon 0.7.9 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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