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.
- checksums.yaml +4 -4
- data/README.md +382 -260
- data/app/assets/javascripts/teaspoon-angular.js +108 -26241
- data/app/assets/javascripts/teaspoon-jasmine.js +103 -2642
- data/app/assets/javascripts/teaspoon-mocha.js +109 -5416
- data/app/assets/javascripts/teaspoon-qunit.js +107 -2255
- data/app/assets/javascripts/teaspoon-teaspoon.js +0 -1
- data/app/assets/javascripts/teaspoon/angular.coffee +3 -1
- data/app/assets/javascripts/teaspoon/base/hook.coffee +21 -0
- data/app/assets/javascripts/teaspoon/base/reporters/html.coffee +26 -14
- data/app/assets/javascripts/teaspoon/base/reporters/html/progress_view.coffee +1 -1
- data/app/assets/javascripts/teaspoon/base/reporters/html/template.coffee +3 -3
- data/app/assets/javascripts/teaspoon/base/teaspoon.coffee +10 -1
- data/app/assets/javascripts/teaspoon/jasmine.coffee +3 -1
- data/app/assets/javascripts/teaspoon/mocha.coffee +3 -1
- data/app/assets/javascripts/teaspoon/mocha/reporters/html.coffee +1 -1
- data/app/assets/javascripts/teaspoon/qunit.coffee +3 -1
- data/app/assets/javascripts/teaspoon/qunit/reporters/html.coffee +1 -1
- data/app/assets/javascripts/teaspoon/teaspoon.coffee +0 -1
- data/app/assets/stylesheets/teaspoon.css +12 -8
- data/app/controllers/teaspoon/suite_controller.rb +32 -0
- data/app/views/teaspoon/suite/_body.html.erb +0 -0
- data/app/views/teaspoon/suite/_boot.html.erb +4 -0
- data/app/views/teaspoon/suite/_boot_require_js.html.erb +19 -0
- data/app/views/teaspoon/{spec/suites.html.erb → suite/index.html.erb} +6 -7
- data/app/views/teaspoon/suite/show.html.erb +19 -0
- data/bin/teaspoon +1 -1
- data/config/routes.rb +14 -4
- data/lib/generators/teaspoon/install/POST_INSTALL +2 -2
- data/lib/generators/teaspoon/install/install_generator.rb +22 -11
- data/lib/generators/teaspoon/install/templates/_body.html.erb +0 -0
- data/lib/generators/teaspoon/install/templates/_boot.html.erb +4 -0
- data/lib/generators/teaspoon/install/templates/jasmine/env.rb +11 -0
- data/lib/generators/teaspoon/install/templates/jasmine/env_comments.rb +182 -0
- data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.coffee +8 -6
- data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.js +8 -7
- data/lib/generators/teaspoon/install/templates/mocha/env.rb +11 -0
- data/lib/generators/teaspoon/install/templates/mocha/env_comments.rb +182 -0
- data/lib/generators/teaspoon/install/templates/mocha/spec_helper.coffee +13 -13
- data/lib/generators/teaspoon/install/templates/mocha/spec_helper.js +13 -13
- data/lib/generators/teaspoon/install/templates/qunit/env.rb +11 -0
- data/lib/generators/teaspoon/install/templates/qunit/env_comments.rb +182 -0
- data/lib/generators/teaspoon/install/templates/qunit/test_helper.coffee +6 -5
- data/lib/generators/teaspoon/install/templates/qunit/test_helper.js +6 -5
- data/lib/tasks/teaspoon.rake +9 -2
- data/lib/teaspoon.rb +4 -6
- data/lib/teaspoon/command_line.rb +116 -134
- data/lib/teaspoon/configuration.rb +144 -66
- data/lib/teaspoon/console.rb +70 -37
- data/lib/teaspoon/coverage.rb +42 -15
- data/lib/teaspoon/deprecated.rb +65 -0
- data/lib/teaspoon/drivers/base.rb +10 -0
- data/lib/teaspoon/drivers/phantomjs/runner.js +9 -11
- data/lib/teaspoon/drivers/phantomjs_driver.rb +21 -21
- data/lib/teaspoon/drivers/selenium_driver.rb +32 -13
- data/lib/teaspoon/engine.rb +32 -12
- data/lib/teaspoon/environment.rb +16 -12
- data/lib/teaspoon/exceptions.rb +41 -5
- data/lib/teaspoon/exporter.rb +52 -0
- data/lib/teaspoon/formatters/base.rb +171 -0
- data/lib/teaspoon/formatters/clean_formatter.rb +2 -4
- data/lib/teaspoon/formatters/documentation_formatter.rb +60 -0
- data/lib/teaspoon/formatters/dot_formatter.rb +12 -90
- data/lib/teaspoon/formatters/json_formatter.rb +36 -0
- data/lib/teaspoon/formatters/junit_formatter.rb +51 -32
- data/lib/teaspoon/formatters/modules/report_module.rb +76 -0
- data/lib/teaspoon/formatters/pride_formatter.rb +23 -27
- data/lib/teaspoon/formatters/snowday_formatter.rb +7 -11
- data/lib/teaspoon/formatters/swayze_or_oprah_formatter.rb +88 -64
- data/lib/teaspoon/formatters/tap_formatter.rb +18 -27
- data/lib/teaspoon/formatters/tap_y_formatter.rb +35 -45
- data/lib/teaspoon/formatters/teamcity_formatter.rb +69 -31
- data/lib/teaspoon/instrumentation.rb +33 -33
- data/lib/teaspoon/result.rb +2 -1
- data/lib/teaspoon/runner.rb +40 -28
- data/lib/teaspoon/server.rb +23 -25
- data/lib/teaspoon/suite.rb +52 -72
- data/lib/teaspoon/utility.rb +3 -14
- data/lib/teaspoon/version.rb +1 -1
- data/spec/dummy/app/assets/javascripts/integration/integration_spec.coffee +3 -0
- data/spec/dummy/app/assets/javascripts/integration/spec_helper.coffee +2 -0
- data/spec/dummy/config/application.rb +3 -0
- data/spec/features/console_reporter_spec.rb +48 -18
- data/spec/features/hooks_spec.rb +23 -41
- data/spec/features/html_reporter_spec.rb +38 -21
- data/spec/features/install_generator_spec.rb +34 -20
- data/spec/features/instrumentation_spec.rb +3 -2
- data/spec/fixtures/coverage.json +243 -0
- data/spec/javascripts/fixtures/_body.html.erb +1 -0
- data/spec/javascripts/jasmine_helper.coffee +1 -1
- data/spec/javascripts/teaspoon/base/fixture_spec.coffee +4 -4
- data/spec/javascripts/teaspoon/base/reporters/html_spec.coffee +9 -10
- data/spec/javascripts/teaspoon/mocha/reporters/html_mspec.coffee +0 -6
- data/spec/javascripts/teaspoon/phantomjs/runner_spec.coffee +5 -6
- data/spec/javascripts/turbolinks_helper.coffee +1 -1
- data/spec/spec_helper.rb +3 -4
- data/spec/teaspoon/command_line_spec.rb +139 -23
- data/spec/teaspoon/configuration_spec.rb +164 -46
- data/spec/teaspoon/console_spec.rb +142 -47
- data/spec/teaspoon/coverage_spec.rb +98 -28
- data/spec/teaspoon/drivers/base_spec.rb +5 -0
- data/spec/teaspoon/drivers/phantomjs_driver_spec.rb +32 -14
- data/spec/teaspoon/drivers/selenium_driver_spec.rb +32 -24
- data/spec/teaspoon/engine_spec.rb +8 -5
- data/spec/teaspoon/environment_spec.rb +56 -33
- data/spec/teaspoon/exceptions_spec.rb +57 -0
- data/spec/teaspoon/exporter_spec.rb +96 -0
- data/spec/teaspoon/formatters/base_spec.rb +259 -0
- data/spec/teaspoon/formatters/clean_formatter_spec.rb +37 -0
- data/spec/teaspoon/formatters/documentation_formatter_spec.rb +127 -0
- data/spec/teaspoon/formatters/dot_formatter_spec.rb +52 -56
- data/spec/teaspoon/formatters/json_formatter_spec.rb +77 -0
- data/spec/teaspoon/formatters/junit_formatter_spec.rb +72 -35
- data/spec/teaspoon/formatters/pride_formatter_spec.rb +37 -0
- data/spec/teaspoon/formatters/snowday_formatter_spec.rb +35 -0
- data/spec/teaspoon/formatters/tap_formatter_spec.rb +29 -81
- data/spec/teaspoon/formatters/tap_y_formatter_spec.rb +31 -141
- data/spec/teaspoon/formatters/teamcity_formatter_spec.rb +99 -42
- data/spec/teaspoon/instrumentation_spec.rb +44 -44
- data/spec/teaspoon/result_spec.rb +37 -0
- data/spec/teaspoon/runner_spec.rb +70 -59
- data/spec/teaspoon/server_spec.rb +34 -52
- data/spec/teaspoon/suite_spec.rb +42 -188
- data/spec/teaspoon_env.rb +39 -28
- data/vendor/assets/javascripts/{angular-scenario-1.0.5.js → angular/1.0.5.js} +0 -0
- data/vendor/assets/javascripts/{angular-scenario-1.0.5.MIT-LICENSE → angular/MIT-LICENSE} +0 -0
- data/vendor/assets/javascripts/{jasmine-1.3.1.js → jasmine/1.3.1.js} +0 -0
- data/vendor/assets/javascripts/jasmine/2.0.0.js +2412 -0
- data/vendor/assets/javascripts/{jasmine-1.3.1.MIT.LICENSE → jasmine/MIT.LICENSE} +0 -0
- data/vendor/assets/javascripts/{mocha-1.10.0.js → mocha/1.10.0.js} +1 -0
- data/vendor/assets/javascripts/mocha/1.17.1.js +5813 -0
- data/vendor/assets/javascripts/{mocha-1.10.1.MIT.LICENSE → mocha/MIT.LICENSE} +0 -0
- data/vendor/assets/javascripts/{qunit-1.12.0.js → qunit/1.12.0.js} +1 -1
- data/vendor/assets/javascripts/qunit/1.14.0.js +2288 -0
- data/vendor/assets/javascripts/{qunit-1.12.0.MIT.LICENSE → qunit/MIT.LICENSE} +0 -0
- data/vendor/assets/javascripts/support/chai.js +827 -385
- data/vendor/assets/javascripts/support/jasmine-jquery-1.7.0.js +720 -0
- data/vendor/assets/javascripts/support/jasmine-jquery-2.0.0.js +812 -0
- data/vendor/assets/javascripts/support/sinon-chai.js +17 -0
- data/vendor/assets/javascripts/support/sinon.js +1138 -643
- metadata +57 -36
- data/app/controllers/teaspoon/spec_controller.rb +0 -38
- data/app/helpers/teaspoon/spec_helper.rb +0 -36
- data/app/views/teaspoon/spec/_require_js.html.erb +0 -21
- data/app/views/teaspoon/spec/_standard.html.erb +0 -4
- data/app/views/teaspoon/spec/runner.html.erb +0 -19
- data/lib/generators/teaspoon/install/templates/env.rb +0 -38
- data/lib/generators/teaspoon/install/templates/jasmine/initializer.rb +0 -64
- data/lib/generators/teaspoon/install/templates/mocha/initializer.rb +0 -64
- data/lib/generators/teaspoon/install/templates/qunit/initializer.rb +0 -64
- data/lib/teaspoon/check_coverage.rb +0 -33
- data/lib/teaspoon/drivers/base_driver.rb +0 -10
- data/lib/teaspoon/exception_handling.rb +0 -18
- data/lib/teaspoon/formatters/base_formatter.rb +0 -63
- data/spec/dummy/config/initializers/teaspoon.rb +0 -41
- data/spec/teaspoon/check_coverage_spec.rb +0 -50
- data/spec/teaspoon/formatters/base_formatter_spec.rb +0 -45
- data/vendor/assets/javascripts/support/chai.MIT.LICENSE +0 -22
- data/vendor/assets/javascripts/support/expect.MIT.LICENSE +0 -22
- data/vendor/assets/javascripts/support/jasmine-jquery.MIT.LICENSE +0 -20
- data/vendor/assets/javascripts/support/jasmine-jquery.js +0 -659
- data/vendor/assets/javascripts/support/sinon-chai.MIT-ISH.LICENSE +0 -13
- 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 <
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
21
|
-
|
|
39
|
+
def log_result(result)
|
|
40
|
+
log_end_suite
|
|
41
|
+
@result = result
|
|
22
42
|
end
|
|
23
43
|
|
|
24
|
-
def
|
|
25
|
-
log
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
81
|
+
log_line("##teamcity[#{str}]")
|
|
35
82
|
end
|
|
36
83
|
|
|
37
84
|
def escape(str)
|
|
38
|
-
{
|
|
39
|
-
|
|
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
|
|
51
|
-
trace.map { |line
|
|
52
|
-
|
|
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.
|
|
8
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
result = process_and_instrument
|
|
31
|
-
length = result.bytesize.to_s
|
|
27
|
+
@response = response
|
|
28
|
+
end
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
def instrumented_response
|
|
31
|
+
status, headers, asset = @response
|
|
32
|
+
headers, asset = [headers.clone, asset.clone]
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
end
|
|
34
|
+
result = add_instrumentation(asset)
|
|
39
35
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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{#{
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
data/lib/teaspoon/result.rb
CHANGED
data/lib/teaspoon/runner.rb
CHANGED
|
@@ -4,56 +4,68 @@ require "teaspoon/result"
|
|
|
4
4
|
module Teaspoon
|
|
5
5
|
class Runner
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
56
|
-
|
|
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
|
data/lib/teaspoon/server.rb
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
+
true
|
|
40
28
|
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
41
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
data/lib/teaspoon/suite.rb
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
26
|
-
|
|
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
|
|
42
|
-
|
|
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
|
|
46
|
-
|
|
37
|
+
def include_spec?(file)
|
|
38
|
+
glob.include?(file)
|
|
47
39
|
end
|
|
48
40
|
|
|
49
|
-
def
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
specs.map { |path|
|
|
55
|
-
file_without_ext = path.split('.').first
|
|
56
|
-
"#{file_without_ext}"
|
|
57
|
-
}
|
|
58
|
-
end
|
|
48
|
+
protected
|
|
59
49
|
|
|
60
|
-
def
|
|
61
|
-
|
|
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
|
|
65
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
"
|
|
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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
|
105
|
-
|
|
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.
|
|
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
|