jasmine-coverage-kikuchiyo-patch 0.0.1
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.
@@ -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
|