jcov 1.0.1 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,14 +7,13 @@ Gem::Specification.new do |s|
7
7
  s.version = JCov::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Doug McInnes"]
10
- s.email = ["dmcinnes@yp.com"]
10
+ s.email = ["doug@dougmcinnes.com"]
11
11
  s.homepage = ""
12
12
  s.summary = %q{Javascript Coverage Tool}
13
13
  s.description = %q{Javascript Coverage Tool}
14
14
 
15
15
  s.add_dependency "commander", "~> 4.0"
16
16
  s.add_dependency "therubyracer", "~> 0.11.1"
17
- s.add_dependency "rkelly", "~> 1.0.4"
18
17
 
19
18
  s.add_development_dependency "cucumber", "~> 1.0"
20
19
  s.add_development_dependency "aruba", "~> 0.4.6"
@@ -1,9 +1,13 @@
1
1
  require 'v8'
2
- require 'rkelly'
3
2
 
4
3
  require 'jcov/version'
4
+ require 'jcov/errors'
5
5
  require 'jcov/configuration'
6
6
  require 'jcov/commands'
7
+ require 'jcov/loader'
8
+ require 'jcov/context'
9
+ require 'jcov/context/run_context'
10
+ require 'jcov/context/instrumentation_context'
7
11
  require 'jcov/runner'
8
12
  require 'jcov/coverage'
9
13
  require 'jcov/reporter'
@@ -4,8 +4,8 @@ module JCov::Commands
4
4
 
5
5
  # the check command
6
6
  class Check
7
- def initialize args, options
8
- config = JCov::Configuration.new options.config
7
+ def initialize(args, options)
8
+ config = JCov::Configuration.new(options)
9
9
 
10
10
  if config.filename
11
11
  puts "Using configuration file: #{config.filename}"
@@ -19,26 +19,26 @@ module JCov::Commands
19
19
 
20
20
  # the run command
21
21
  class Run
22
- def initialize args, options
22
+ def initialize(args, options)
23
23
  # default to no color unless we're on a tty
24
24
  options.default :color => $stdout.tty?
25
25
  options.default :coverage => true
26
26
 
27
27
  options.args = args
28
28
 
29
- config = JCov::Configuration.new(options.config)
29
+ config = JCov::Configuration.new(options)
30
30
 
31
- runner = JCov::Coverage::CoverageRunner.new(config, options)
31
+ runner = JCov::Runner.new(config)
32
32
 
33
33
  runner.run
34
34
 
35
35
  abort "Test Failures! :(" if runner.failure_count > 0
36
36
 
37
37
  if options.report
38
- JCov::Reporter::HTMLReporter.new(runner).report
38
+ JCov::Reporter::HTMLReporter.new(runner.coverage).report
39
39
  end
40
40
 
41
- reporter = JCov::Reporter::ConsoleReporter.new(runner)
41
+ reporter = JCov::Reporter::ConsoleReporter.new(runner.coverage)
42
42
 
43
43
  abort unless reporter.report
44
44
  end
@@ -14,11 +14,17 @@ module JCov
14
14
  "test_runner" => "test/javascripts/runner.js",
15
15
  "error_field" => "error_count",
16
16
  "report_output_directory" => "jcov",
17
+ "verbose" => false,
18
+ "color" => true,
19
+ "coverage" => true,
20
+ "report" => false,
21
+ "dump" => false
17
22
  }
18
23
 
19
- def initialize file
20
- @filename = find_file(file)
21
- @config = DEFAULTS.merge(@filename && YAML.load_file(@filename) || {})
24
+ def initialize(options)
25
+ @filename = find_file(options.config)
26
+ @config = DEFAULTS.merge(options.__hash__)
27
+ @config.merge!(YAML.load_file(@filename)) if @filename
22
28
  create_readers
23
29
  end
24
30
 
@@ -0,0 +1,17 @@
1
+ module JCov::Context
2
+
3
+ # create a V8 context object with our context methods mapped onto it
4
+ def create
5
+ @context = V8::Context.new
6
+
7
+ # map our context objects methods onto the v8 object
8
+ # doing this instead of using :with because of this:
9
+ # https://github.com/cowboyd/therubyracer/issues/251
10
+ (self.methods - Object.new.methods - [:create]).each do |method|
11
+ @context[method] = self.method(method)
12
+ end
13
+
14
+ @context
15
+ end
16
+
17
+ end
@@ -0,0 +1,31 @@
1
+ module JCov::Context
2
+
3
+ class InstrumentationContext
4
+ include JCov::Context
5
+
6
+ def initialize(coverage_data)
7
+ @coverage_data = coverage_data
8
+ end
9
+
10
+ # called when a line can be covered by tests
11
+ def lineCovered(file, line, column)
12
+ # only record if the line hasn't been recorded yet
13
+ # or if it has, the line is not flagged with nil
14
+ if !@coverage_data[file].has_key?(line) || !@coverage_data[file].nil?
15
+ @coverage_data[file][line] = column
16
+ end
17
+ end
18
+
19
+ # called when a line should be ignored in the coverage report
20
+ def ignoreLine(file, line)
21
+ @coverage_data[file][line] = nil # set to nil so it's ignored
22
+ end
23
+
24
+ # for testing
25
+ def print(str)
26
+ puts str
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,44 @@
1
+ module JCov::Context
2
+
3
+ class RunContext
4
+ include JCov::Context
5
+
6
+ def initialize(loader)
7
+ @loader = loader
8
+ @coverage_data = loader.coverage_data
9
+ end
10
+
11
+ def print(s)
12
+ Kernel.print s
13
+ end
14
+
15
+ def println(*s)
16
+ Kernel.puts s
17
+ end
18
+
19
+ # so we can do our dotted line output
20
+ def putc(char)
21
+ $stdout.putc(char);
22
+ $stdout.flush();
23
+ end
24
+
25
+ # for JSpec
26
+ def readFile(file)
27
+ File.read(file)
28
+ end
29
+
30
+ def load(file)
31
+ # use the configured loader
32
+ content = @loader.load file
33
+
34
+ # evaluate the javascript
35
+ @context.eval(content, file)
36
+ end
37
+
38
+ # increment
39
+ def _coverage_tick(file, line)
40
+ @coverage_data[file][line] += 1
41
+ end
42
+ end
43
+
44
+ end
@@ -1,151 +1,76 @@
1
1
  module JCov
2
- module Coverage
3
2
 
4
- # extend RKelly's ECMAVisitor to include our coverage_tick
5
- class CoverageVisitor < RKelly::Visitors::ECMAVisitor
6
- def initialize(coverage)
7
- @coverage = coverage
8
- @indent = 0
9
- end
10
-
11
- # whenever we hit a line, add the instrumentation
12
- def visit_SourceElementsNode(o)
13
- o.value.map { |x|
14
- if (x.filename && x.line)
15
- coverage = "_coverage_tick('#{x.filename}', #{x.line});"
16
- @coverage[x.filename][x.line] = 0
17
- end
18
- "#{coverage || ""}#{indent}#{x.accept(self)}"
19
- }.join("\n")
20
- end
21
- end
22
-
23
-
24
- class CoverageRunner
25
- attr_reader :config
26
- attr_reader :options
27
- attr_reader :runner
28
- attr_reader :instrumented_files
29
-
30
- def initialize config, options
31
- @config = config
32
- @options = options
33
-
34
- @runner = JCov::Runner.new(config, options)
3
+ class Coverage
35
4
 
36
- override_runners_load_method
37
- add_coverage_method_to_context
5
+ attr_accessor :loader
6
+ attr_accessor :config
38
7
 
39
- @visitor = CoverageVisitor.new(coverage_data)
40
- @parser = RKelly::Parser.new
8
+ def initialize(config)
9
+ @config = config
41
10
 
42
- @instrumented_files = {}
43
- end
44
-
45
- def coverable_files
46
- if @coverable_files.nil?
47
- # all the files we're testing on
48
- @coverable_files = Dir.glob(File.join(config.source_directory, "**", "*.js"))
49
- # only run coverage on files that we haven't specifically ignored
50
- ignore = config.ignore || []
51
- @coverable_files.delete_if {|file| ignore.any? {|i| file.match(i) }}
52
- # remove the runner if it's in there
53
- @coverable_files.delete(config.test_runner)
54
- end
55
- @coverable_files
56
- end
11
+ # for loading and instrumenting fiels
12
+ @loader = JCov::Loader.new(coverable_files, :dump => config.dump)
13
+ end
57
14
 
58
- def coverage_data
59
- if @coverage_data.nil?
60
- # set up coverage data structure
61
- @coverage_data = {}
62
- coverable_files.each {|file| @coverage_data[file] = {} }
63
- end
64
- @coverage_data
15
+ def coverable_files
16
+ if @coverable_files.nil?
17
+ # all the files we're testing on
18
+ @coverable_files = Dir.glob(File.join(@config.source_directory, "**", "*.js"))
19
+ # only run coverage on files that we haven't specifically ignored
20
+ ignore = @config.ignore || []
21
+ @coverable_files.delete_if {|file| ignore.any? {|i| file.match(i) }}
22
+ # remove the runner if it's in there
23
+ @coverable_files.delete(@config.test_runner)
65
24
  end
25
+ @coverable_files
26
+ end
66
27
 
67
- # our new load method
68
- def load file
69
- if instrumented_files[file]
70
- # reuse previously loaded file
71
- content = instrumented_files[file]
72
- else
73
- content = File.read(file)
74
-
75
- # is this a file we need to instrument?
76
- if coverable_files.include? file
77
- # run it through the js parser and custom renderer
78
- tree = @parser.parse(content, file)
79
- content = @visitor.accept(tree)
80
-
81
- # cache the file if it's reloaded
82
- instrumented_files[file] = content
28
+ # reduce the coverage data to file, total line count, and covered line count
29
+ def reduced_coverage_data
30
+ # cache the result
31
+ @reduced_coverage_data ||=
32
+ coverage_data.map do |file, lines|
33
+ # if we don't have any data for this file it was never loaded
34
+ if lines.empty?
35
+ # load it now
36
+ lines = examine_uncovered_file file
37
+
38
+ # this file was never run so it has zero coverage
39
+ cover = 0
40
+ else
41
+ # munge the count data together to get coverage
42
+ cover = lines.values.compact.inject(0) { |memo, count| memo + ((count > 0) ? 1 : 0) }
83
43
  end
84
- end
85
44
 
86
- runner.context.eval(content, file)
87
- end
88
-
89
- def _coverage_tick file, line
90
- coverage_data[file][line] += 1
91
- end
92
-
93
- def run
94
- runner.run
95
- end
96
-
97
- # proxy to runner
98
- def failure_count
99
- runner.failure_count
100
- end
45
+ # ignore nil values
46
+ total = lines.values.compact.count
101
47
 
102
- # reduce the coverage data to file, total line count, and covered line count
103
- def reduced_coverage_data
104
- if @reduced_coverage_data.nil?
105
- @reduced_coverage_data = coverage_data.map do |file, lines|
106
- # if we don't have any data for this file it was never loaded
107
- if lines.empty?
108
- # load it now
109
- content = File.read(file)
110
-
111
- # run it through the js parser and custom renderer
112
- # the visitor will fill out the coverage data for this line
113
- tree = @parser.parse(content, file)
114
- content = @visitor.accept(tree)
115
-
116
- # re-get the lines
117
- lines = coverage_data[file]
118
-
119
- # this file was never run
120
- cover = 0
121
- else
122
- # munge the count data together to get coverage
123
- cover = lines.values.inject(0) { |memo, count| memo + ((count > 0) ? 1 : 0) }
124
- end
125
-
126
- total = lines.count
127
-
128
- [file, total, cover]
129
- end
48
+ [file, total, cover]
130
49
  end
131
- @reduced_coverage_data
132
- end
50
+ end
133
51
 
134
- def get_binding
135
- binding
136
- end
52
+ def coverage_data
53
+ @loader.coverage_data
54
+ end
137
55
 
138
- private
56
+ # for coverage reporting
57
+ def get_binding
58
+ binding
59
+ end
139
60
 
140
- def add_coverage_method_to_context
141
- runner.context['_coverage_tick'] = self.method('_coverage_tick')
142
- end
61
+ private
143
62
 
144
- def override_runners_load_method
145
- runner.context['load'] = self.method('load')
146
- end
63
+ # when a file is in the list to be examined but never loaded
64
+ # we have to load it ourselves to calculate the full coverage counts
65
+ def examine_uncovered_file(filename)
66
+ # run it through the Loader
67
+ # it will fill out the coverage data for this file
68
+ @loader.load filename
147
69
 
70
+ # re-get the lines
71
+ coverage_data[filename]
148
72
  end
149
73
 
150
74
  end
75
+
151
76
  end
@@ -0,0 +1,6 @@
1
+ module JCov
2
+
3
+ class ParseError < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,44 @@
1
+ // for acorn
2
+ var self = this;
3
+
4
+ JCov = (function () {
5
+
6
+ var calculateCoverageData = function (code, file) {
7
+ var tree = acorn.parse(code, {locations: true});
8
+
9
+ acorn.walk.simple(tree, {
10
+ Statement: function (node) {
11
+ // cover all statements
12
+ if (node.type !== "BlockStatement" &&
13
+ node.type !== "EmptyStatement") {
14
+ var pos = node.loc.start;
15
+ lineCovered(file, pos.line, pos.column);
16
+ }
17
+ },
18
+ IfStatement: function (node) {
19
+ // ignore else statements
20
+ if (node.alternate) {
21
+ ignoreLine(file, node.alternate.loc.start.line);
22
+ }
23
+
24
+ if (node.consequent.type !== "BlockStatement") {
25
+ var ifStart = node.loc.start.line;
26
+ var thenStart = node.consequent.loc.start.line;
27
+ if (ifStart < thenStart) {
28
+ // ignore non-block single if statements if they're on
29
+ // their own line
30
+ ignoreLine(file, node.consequent.loc.start.line);
31
+ } else if (ifStart === thenStart) {
32
+ // single line if statement, mark the whole line
33
+ lineCovered(file, ifStart, 0);
34
+ }
35
+ }
36
+ }
37
+ });
38
+ };
39
+
40
+ return {
41
+ calculateCoverageData: calculateCoverageData
42
+ };
43
+
44
+ })();