jasmine-coverage-kikuchiyo-patch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
|
|
1
|
+
/**
|
2
|
+
* Note, strictly speaking this isn't a spec.
|
3
|
+
* But we must run it like one so it occurs in the same context
|
4
|
+
* as the other tests that have run. In that way, we can
|
5
|
+
* call out in javascript and get the resulting coverage reports from the instrumented
|
6
|
+
* files.
|
7
|
+
*
|
8
|
+
* Further, when we log the results to console, the file logger captures that.
|
9
|
+
*/
|
10
|
+
describe("jasmine-coverage", function () {
|
11
|
+
|
12
|
+
it("is generating a coverage report", function () {
|
13
|
+
// Output the complete line by line coverage report for capture by the file logger
|
14
|
+
generateEncodedCoverage();
|
15
|
+
|
16
|
+
// Get the simple percentages for each file
|
17
|
+
coverageForAllFiles();
|
18
|
+
});
|
19
|
+
|
20
|
+
});
|
21
|
+
|
22
|
+
String.prototype.lpad = function (padString, length) {
|
23
|
+
var str = this;
|
24
|
+
while (str.length < length)
|
25
|
+
str = padString + str;
|
26
|
+
return str;
|
27
|
+
};
|
28
|
+
|
29
|
+
function generateEncodedCoverage() {
|
30
|
+
var rv = {};
|
31
|
+
for (var file_name in window._$jscoverage) {
|
32
|
+
var jscov = window._$jscoverage[ file_name ];
|
33
|
+
var file_report = rv[ file_name ] = {
|
34
|
+
coverage:new Array(jscov.length),
|
35
|
+
source:new Array(jscov.length)
|
36
|
+
};
|
37
|
+
for (var i = 0; i < jscov.length; ++i) {
|
38
|
+
var hit_count = jscov[ i ] !== undefined ? jscov[ i ] : null;
|
39
|
+
|
40
|
+
file_report.coverage[ i ] = hit_count;
|
41
|
+
file_report.source[ i ] = jscov.source[ i ];
|
42
|
+
}
|
43
|
+
}
|
44
|
+
console.log("ENCODED-COVERAGE-EXPORT-STARTS:" + Base64.encode(JSON.stringify(rv)));
|
45
|
+
console.log("\nENCODED-COVERAGE-EXPORT-ENDS\n");
|
46
|
+
}
|
47
|
+
|
48
|
+
function coverageForAllFiles() {
|
49
|
+
|
50
|
+
var totals = { files:0, statements:0, executed:0 };
|
51
|
+
|
52
|
+
var output = "Coverage was:\n";
|
53
|
+
|
54
|
+
for (var file_name in window._$jscoverage) {
|
55
|
+
var jscov = window._$jscoverage[ file_name ];
|
56
|
+
var simple_file_coverage = coverageForFile(jscov);
|
57
|
+
|
58
|
+
totals['files']++;
|
59
|
+
totals['statements'] += simple_file_coverage['statements'];
|
60
|
+
totals['executed'] += simple_file_coverage['executed'];
|
61
|
+
|
62
|
+
var fraction = (simple_file_coverage['executed']+"/"+simple_file_coverage['statements']).lpad(' ', 10);
|
63
|
+
output += fraction + (" = " + simple_file_coverage['percentage'] + "").lpad(' ', 3) + "% for " + file_name + "\n";
|
64
|
+
}
|
65
|
+
|
66
|
+
var coverage = parseInt(100 * totals['executed'] / totals['statements']);
|
67
|
+
if (isNaN(coverage)) {
|
68
|
+
coverage = 0;
|
69
|
+
}
|
70
|
+
|
71
|
+
if (totals['statements'] === 0) {
|
72
|
+
log("No Javascript was found to test coverage for.");
|
73
|
+
} else {
|
74
|
+
output += ( totals['executed'] +"/"+totals['statements']+ " = "+ coverage + "").lpad(' ', 15) + "% Total\n";
|
75
|
+
log(output);
|
76
|
+
}
|
77
|
+
|
78
|
+
return coverage;
|
79
|
+
}
|
80
|
+
|
81
|
+
|
82
|
+
function coverageForFile(fileCC) {
|
83
|
+
var lineNumber;
|
84
|
+
var num_statements = 0;
|
85
|
+
var num_executed = 0;
|
86
|
+
var missing = [];
|
87
|
+
var length = fileCC.length;
|
88
|
+
var currentConditionalEnd = 0;
|
89
|
+
var conditionals = null;
|
90
|
+
if (fileCC.conditionals) {
|
91
|
+
conditionals = fileCC.conditionals;
|
92
|
+
}
|
93
|
+
for (lineNumber = 0; lineNumber < length; lineNumber++) {
|
94
|
+
var n = fileCC[lineNumber];
|
95
|
+
|
96
|
+
if (lineNumber === currentConditionalEnd) {
|
97
|
+
currentConditionalEnd = 0;
|
98
|
+
}
|
99
|
+
else if (currentConditionalEnd === 0 && conditionals && conditionals[lineNumber]) {
|
100
|
+
currentConditionalEnd = conditionals[lineNumber];
|
101
|
+
}
|
102
|
+
|
103
|
+
if (currentConditionalEnd !== 0) {
|
104
|
+
continue;
|
105
|
+
}
|
106
|
+
|
107
|
+
if (n === undefined || n === null) {
|
108
|
+
continue;
|
109
|
+
}
|
110
|
+
|
111
|
+
if (n === 0) {
|
112
|
+
missing.push(lineNumber);
|
113
|
+
}
|
114
|
+
else {
|
115
|
+
num_executed++;
|
116
|
+
}
|
117
|
+
num_statements++;
|
118
|
+
}
|
119
|
+
|
120
|
+
var percentage = ( num_statements === 0 ? 0 : parseInt(100 * num_executed / num_statements) );
|
121
|
+
|
122
|
+
return {
|
123
|
+
statements:num_statements,
|
124
|
+
executed:num_executed,
|
125
|
+
percentage:percentage
|
126
|
+
};
|
127
|
+
}
|
@@ -0,0 +1,136 @@
|
|
1
|
+
env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
2
|
+
if env =~ /^(development|test)$/
|
3
|
+
require 'rake'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
namespace :jasmine do
|
7
|
+
desc 'Runs jasmine with a coverage report'
|
8
|
+
task :coverage do
|
9
|
+
|
10
|
+
require 'jasmine-headless-webkit'
|
11
|
+
# Instill our patches for jasmine-headless to work
|
12
|
+
require_relative 'jasmine_headless_coverage_patches'
|
13
|
+
|
14
|
+
# We use jasmine-headless-webkit, since it has excellent programmatic integration with Jasmine
|
15
|
+
# But... the 'headless' part of it doesn't work on TeamCity, so we use the headless gem
|
16
|
+
require 'headless'
|
17
|
+
|
18
|
+
headless = Headless.new
|
19
|
+
headless.start
|
20
|
+
|
21
|
+
# Preprocess the JS files to add instrumentation
|
22
|
+
output_dir = File.expand_path('target/jscoverage/')
|
23
|
+
instrumented_dir = output_dir+'/instrumented/'
|
24
|
+
FileUtils.rm_rf output_dir
|
25
|
+
FileUtils.mkdir_p instrumented_dir
|
26
|
+
|
27
|
+
# The reprocessing folder map
|
28
|
+
files_map = {
|
29
|
+
File.expand_path('app/assets/javascripts') => instrumented_dir+'app',
|
30
|
+
File.expand_path('lib/assets/javascripts') => instrumented_dir+'lib',
|
31
|
+
File.expand_path('public/javascripts') => instrumented_dir+'public'
|
32
|
+
}
|
33
|
+
|
34
|
+
# Instrument the source files into the instrumented folders
|
35
|
+
files_map.keys.each do |folder|
|
36
|
+
instrument(folder, files_map[folder])
|
37
|
+
# Also hoist up the eventual viewing files
|
38
|
+
FileUtils.mv(Dir.glob(files_map[folder]+'/jscoverage*'), output_dir)
|
39
|
+
end
|
40
|
+
|
41
|
+
Jasmine::Coverage.resources = files_map
|
42
|
+
Jasmine::Coverage.output_dir = output_dir
|
43
|
+
|
44
|
+
puts "\nCoverage will now be run. Expect a large block of compiled coverage data. This will be processed for you into target/jscoverage.\n\n"
|
45
|
+
|
46
|
+
# Run Jasmine using the original config.
|
47
|
+
status_code = Jasmine::Headless::Runner.run(
|
48
|
+
# Any options from the options.rb file in jasmine-headless-webkit can be used here.
|
49
|
+
|
50
|
+
:reporters => [['File', "#{output_dir}/rawreport.txt"]]
|
51
|
+
)
|
52
|
+
errStr = "JSCoverage exited with error code: #{status_code}.\nThis implies one of five things:\n"
|
53
|
+
errStr = errStr +"0) Your JS files had exactly zero instructions. Are they all blank or just comments?\n"
|
54
|
+
errStr = errStr +"1) A test failed (run bundle exec jasmine:headless to see a better error)\n"
|
55
|
+
errStr = errStr +"2) The sourcecode has a syntax error (which JSLint should find)\n"
|
56
|
+
errStr = errStr +"3) An error occurred in a deferred block, eg a setTimeout or underscore _.defer. This caused a window error which Jasmine will never see.\n"
|
57
|
+
errStr = errStr +"4) The source files are being loaded out of sequence (so global variables are not being declared in order)\n"
|
58
|
+
errStr = errStr +" To check this, run bundle exec jasmine-headless-webkit -l to see the ordering\n"
|
59
|
+
errStr = errStr +"\nIn any case, try running the standard jasmine command to get better errors:\n\nbundle exec jasmine:headless\n"
|
60
|
+
errStr = errStr +"\nFinally, try opening the testrig in firefox to see the tests run in a browser and get a stacktrace. "
|
61
|
+
errStr = errStr +"Chrome has strict security settings that make this difficult since it accesses the local filesystem from Javascript (but you can switch the settings off at the command line)\n\n"
|
62
|
+
errStr = errStr +"\n**********************************************************************************************\n"
|
63
|
+
errStr = errStr +"\nThe test rig file needs to load JS directly off disk, which Chrome prevents by default. Your best bet is to open the rig in Firefox.\n"
|
64
|
+
errStr = errStr +"\nThe file can be found here: #{Jasmine::Coverage.output_dir}/testrig/jscoverage-test-rig.html\n"
|
65
|
+
errStr = errStr +"\n**********************************************************************************************\n"
|
66
|
+
fail errStr if status_code == 1
|
67
|
+
|
68
|
+
# Obtain the console log, which includes the coverage report encoded within it
|
69
|
+
contents = File.open("#{output_dir}/rawreport.txt") { |f| f.read }
|
70
|
+
# Get our Base64.
|
71
|
+
json_report_enc = contents.split(/ENCODED-COVERAGE-EXPORT-STARTS:/m)[1]
|
72
|
+
# Remove the junk at the end
|
73
|
+
json_report_enc_stripped = json_report_enc[0, json_report_enc.index("\"")]
|
74
|
+
|
75
|
+
# Unpack it from Base64
|
76
|
+
json_report = Base64.decode64(json_report_enc_stripped)
|
77
|
+
|
78
|
+
# Save the coverage report where the GUI html expects it to be
|
79
|
+
File.open("#{output_dir}/jscoverage.json", 'w') { |f| f.write(json_report) }
|
80
|
+
|
81
|
+
# Modify the jscoverage.html so it knows it is showing a report, not running a test
|
82
|
+
File.open("#{output_dir}/jscoverage.js", 'a') { |f| f.write("\njscoverage_isReport = true;") }
|
83
|
+
|
84
|
+
if json_report_enc.index("No Javascript was found to test coverage for").nil?
|
85
|
+
# Check for coverage failure
|
86
|
+
total_location = json_report_enc.index("% Total")
|
87
|
+
coverage_pc = json_report_enc[total_location-3, 3].to_i
|
88
|
+
|
89
|
+
conf = (ENV['JSCOVERAGE_MINIMUM'] || ENV['JASMINE_COVERAGE_MINIMUM'])
|
90
|
+
fail "Coverage Fail: Javascript coverage was less than #{conf}%. It was #{coverage_pc}%." if conf && coverage_pc < conf.to_i
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
def instrument folder, instrumented_sub_dir
|
96
|
+
return if !File.directory? folder
|
97
|
+
FileUtils.mkdir_p instrumented_sub_dir
|
98
|
+
puts "Locating jscoverage..."
|
99
|
+
system "which jscoverage"
|
100
|
+
puts "Instrumenting JS files..."
|
101
|
+
jsc_status = system "jscoverage -v #{folder} #{instrumented_sub_dir}"
|
102
|
+
if jsc_status != true
|
103
|
+
puts "jscoverage failed with status '#{jsc_status}'. Is jscoverage on your path? Path follows:"
|
104
|
+
system "echo $PATH"
|
105
|
+
puts "Result of calling jscoverage with no arguments follows:"
|
106
|
+
system "jscoverage"
|
107
|
+
fail "Unable to use jscoverage"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
module Jasmine
|
113
|
+
module Coverage
|
114
|
+
@resources
|
115
|
+
|
116
|
+
def self.resources= resources
|
117
|
+
@resources = resources
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.resources
|
121
|
+
@resources
|
122
|
+
end
|
123
|
+
|
124
|
+
@output_dir
|
125
|
+
|
126
|
+
def self.output_dir= output_dir
|
127
|
+
@output_dir = output_dir
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.output_dir
|
131
|
+
@output_dir
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# This file holds the monkeypatches to open up jasmine headless for jasmine coverage.
|
2
|
+
|
3
|
+
|
4
|
+
# This patch writes out a copy of the file that was loaded into the JSCoverage context for testing.
|
5
|
+
# You can look at it to see if it included all the files and tests you expect.
|
6
|
+
require 'jasmine/headless/template_writer'
|
7
|
+
module Jasmine::Headless
|
8
|
+
class TemplateWriter
|
9
|
+
alias old_write :write
|
10
|
+
|
11
|
+
def write
|
12
|
+
ret = old_write
|
13
|
+
str = File.open(all_tests_filename, "rb").read
|
14
|
+
|
15
|
+
testrigfolder = Jasmine::Coverage.output_dir+"/testrig"
|
16
|
+
FileUtils.mkdir_p testrigfolder
|
17
|
+
|
18
|
+
p "\nCopying all view files and potential javascript fixture folders so the JS has access to the html fixtures.\n"
|
19
|
+
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/../../spec", "#{testrigfolder}/spec")
|
20
|
+
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/../../app", "#{testrigfolder}/app")
|
21
|
+
FileUtils.mkdir_p "#{testrigfolder}/target/fixtures"
|
22
|
+
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/../fixtures", "#{testrigfolder}/target/fixtures")
|
23
|
+
FileUtils.mkdir_p "#{testrigfolder}/target/views"
|
24
|
+
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/../views", "#{testrigfolder}/target/views")
|
25
|
+
|
26
|
+
jss = str.scan(/<script type="text\/javascript" src="(.*)"><\/script>/)
|
27
|
+
jss << str.scan(/<link rel="stylesheet" href="(.*)" type="text\/css" \/>/)
|
28
|
+
jss << str.scan(/\.coffee\.js'\] = '(.*)';<\/script>/)
|
29
|
+
jss.flatten!
|
30
|
+
jss.each { |s|
|
31
|
+
js = File.basename(s)
|
32
|
+
str.sub!(s, js)
|
33
|
+
if File.exists?("#{testrigfolder}/#{js}") && js != 'index.js'
|
34
|
+
s = "\n\n*******************************************************************\n"
|
35
|
+
s = s + "Cannot copy file '#{js}' into jasmine coverage test rig folder.\n"
|
36
|
+
s = s + "There is already another file of that name. You either have two files with the same name (but in different paths)\n"
|
37
|
+
s = s + "or your filename is the same as that from a third party vendor.\n"
|
38
|
+
s = s + "*******************************************************************\n\n"
|
39
|
+
raise s
|
40
|
+
end
|
41
|
+
FileUtils.cp(s, testrigfolder)
|
42
|
+
}
|
43
|
+
|
44
|
+
outfile = "#{testrigfolder}/jscoverage-test-rig.html"
|
45
|
+
aFile = File.new(outfile, "w")
|
46
|
+
aFile.write(str)
|
47
|
+
aFile.close
|
48
|
+
|
49
|
+
puts "A copy of the complete page that was used as the test environment can be found here:"
|
50
|
+
puts "#{outfile}"
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Here we patch the resource handler to output the location of our instrumented files
|
57
|
+
module Jasmine::Headless
|
58
|
+
class FilesList
|
59
|
+
|
60
|
+
alias old_to_html :to_html
|
61
|
+
|
62
|
+
def to_html(files)
|
63
|
+
# Declare our test runner files
|
64
|
+
cov_files = ['/jscoverage.js', '/coverage_output_generator.js']
|
65
|
+
|
66
|
+
# Add the original files, remapping to instrumented where necessary
|
67
|
+
tags = []
|
68
|
+
(old_to_html files).each do |path|
|
69
|
+
files_map = Jasmine::Coverage.resources
|
70
|
+
files_map.keys.each do |folder|
|
71
|
+
path = path.sub(folder, files_map[folder])
|
72
|
+
|
73
|
+
# Here we must check the supplied config hasn't pulled in our jscoverage runner file.
|
74
|
+
# If it has, the tests will fire too early, capturing only minimal coverage
|
75
|
+
if cov_files.select { |f| path.include?(f) }.length > 0
|
76
|
+
fail "Assets defined by jasmine.yml must not include any of #{cov_files}: #{path}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
tags << path
|
81
|
+
end
|
82
|
+
|
83
|
+
# Attach the "in context" test runners
|
84
|
+
tags = tags + old_to_html(cov_files.map { |f| File.dirname(__FILE__)+f })
|
85
|
+
|
86
|
+
tags
|
87
|
+
end
|
88
|
+
|
89
|
+
alias old_sprockets_environment :sprockets_environment
|
90
|
+
|
91
|
+
def sprockets_environment
|
92
|
+
return @sprockets_environment if @sprockets_environment
|
93
|
+
old_sprockets_environment
|
94
|
+
# Add the location of our jscoverage.js
|
95
|
+
@sprockets_environment.append_path(File.dirname(__FILE__))
|
96
|
+
@sprockets_environment
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|