piggly 1.2.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.
- data/README.markdown +84 -0
- data/Rakefile +19 -0
- data/bin/piggly +245 -0
- data/lib/piggly/compiler/cache.rb +151 -0
- data/lib/piggly/compiler/pretty.rb +67 -0
- data/lib/piggly/compiler/queue.rb +46 -0
- data/lib/piggly/compiler/tags.rb +244 -0
- data/lib/piggly/compiler/trace.rb +91 -0
- data/lib/piggly/compiler.rb +5 -0
- data/lib/piggly/config.rb +43 -0
- data/lib/piggly/filecache.rb +40 -0
- data/lib/piggly/installer.rb +95 -0
- data/lib/piggly/parser/grammar.tt +747 -0
- data/lib/piggly/parser/nodes.rb +319 -0
- data/lib/piggly/parser/parser.rb +11783 -0
- data/lib/piggly/parser/traversal.rb +48 -0
- data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
- data/lib/piggly/parser.rb +67 -0
- data/lib/piggly/profile.rb +87 -0
- data/lib/piggly/reporter/html.rb +207 -0
- data/lib/piggly/reporter/piggly.css +187 -0
- data/lib/piggly/reporter/sortable.js +493 -0
- data/lib/piggly/reporter.rb +21 -0
- data/lib/piggly/task.rb +64 -0
- data/lib/piggly/util.rb +28 -0
- data/lib/piggly/version.rb +15 -0
- data/lib/piggly.rb +18 -0
- data/spec/compiler/cache_spec.rb +9 -0
- data/spec/compiler/pretty_spec.rb +9 -0
- data/spec/compiler/queue_spec.rb +3 -0
- data/spec/compiler/rewrite_spec.rb +3 -0
- data/spec/compiler/tags_spec.rb +285 -0
- data/spec/compiler/trace_spec.rb +173 -0
- data/spec/config_spec.rb +58 -0
- data/spec/filecache_spec.rb +70 -0
- data/spec/fixtures/snippets.sql +158 -0
- data/spec/grammar/expression_spec.rb +302 -0
- data/spec/grammar/statements/assignment_spec.rb +70 -0
- data/spec/grammar/statements/exception_spec.rb +52 -0
- data/spec/grammar/statements/if_spec.rb +178 -0
- data/spec/grammar/statements/loop_spec.rb +41 -0
- data/spec/grammar/statements/sql_spec.rb +71 -0
- data/spec/grammar/tokens/comment_spec.rb +58 -0
- data/spec/grammar/tokens/datatype_spec.rb +52 -0
- data/spec/grammar/tokens/identifier_spec.rb +58 -0
- data/spec/grammar/tokens/keyword_spec.rb +44 -0
- data/spec/grammar/tokens/label_spec.rb +40 -0
- data/spec/grammar/tokens/literal_spec.rb +30 -0
- data/spec/grammar/tokens/lval_spec.rb +50 -0
- data/spec/grammar/tokens/number_spec.rb +34 -0
- data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
- data/spec/grammar/tokens/string_spec.rb +54 -0
- data/spec/grammar/tokens/whitespace_spec.rb +40 -0
- data/spec/parser_spec.rb +8 -0
- data/spec/profile_spec.rb +5 -0
- data/spec/reporter/html_spec.rb +0 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/spec_suite.rb +5 -0
- metadata +121 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module Piggly
|
2
|
+
|
3
|
+
#
|
4
|
+
# Routines for traversing a tree; assumes base class defines elements
|
5
|
+
# as a method that returns a list of child nodes
|
6
|
+
#
|
7
|
+
module NodeTraversal
|
8
|
+
def fold_down(init) # :yields: NodeClass => init
|
9
|
+
if elements
|
10
|
+
elements.inject(yield(init, self)) do |state, e|
|
11
|
+
e.fold_down(state){|succ, n| yield(succ, n) }
|
12
|
+
end
|
13
|
+
else
|
14
|
+
yield(init, self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def count # :yields: NodeClass => boolean
|
19
|
+
fold_down(0){|sum, e| yield(e) ? sum + 1 : sum }
|
20
|
+
end
|
21
|
+
|
22
|
+
def find # :yields: NodeClass => boolean
|
23
|
+
found = false
|
24
|
+
catch :done do
|
25
|
+
fold_down(nil) do |_,e|
|
26
|
+
if yield(e)
|
27
|
+
found = e
|
28
|
+
throw :done
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
found
|
33
|
+
end
|
34
|
+
|
35
|
+
def select # :yields: NodeClass => boolean
|
36
|
+
fold_down([]){|list,e| yield(e) ? list << e : list }
|
37
|
+
end
|
38
|
+
|
39
|
+
def flatten # :yields: NodeClass
|
40
|
+
if block_given?
|
41
|
+
fold_down([]){|list,e| list << yield(e) }
|
42
|
+
else
|
43
|
+
fold_down([]){|list,e| list << e }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Piggly
|
2
|
+
|
3
|
+
#
|
4
|
+
# Pl/pgSQL Parser, returns a tree of NodeClass values (see nodes.rb)
|
5
|
+
#
|
6
|
+
class Parser
|
7
|
+
include FileCache
|
8
|
+
|
9
|
+
class Failure < RuntimeError; end
|
10
|
+
|
11
|
+
# Returns parse tree
|
12
|
+
def self.parse(string)
|
13
|
+
p = parser
|
14
|
+
|
15
|
+
begin
|
16
|
+
# downcase input for case-insensitive parsing,
|
17
|
+
# then restore original string after parsing
|
18
|
+
input = string.downcase
|
19
|
+
tree = p.parse(input)
|
20
|
+
tree or raise Failure, "#{p.failure_reason}"
|
21
|
+
rescue Failure
|
22
|
+
$!.backtrace.clear
|
23
|
+
raise
|
24
|
+
ensure
|
25
|
+
input.replace string
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parser_path; File.join(File.dirname(__FILE__), 'parser', 'parser.rb') end
|
30
|
+
def self.grammar_path; File.join(File.dirname(__FILE__), 'parser', 'grammar.tt') end
|
31
|
+
def self.nodes_path; File.join(File.dirname(__FILE__), 'parser', 'nodes.rb') end
|
32
|
+
|
33
|
+
def self.stale?(source)
|
34
|
+
File.stale?(cache_path(source), source, grammar_path, parser_path, nodes_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.cache(source)
|
38
|
+
cache = cache_path(source)
|
39
|
+
|
40
|
+
if stale?(source)
|
41
|
+
tree = parse(File.read(source))
|
42
|
+
File.open(cache, 'w+') do |f|
|
43
|
+
Marshal.dump(tree, f)
|
44
|
+
tree
|
45
|
+
end
|
46
|
+
else
|
47
|
+
_ = parser # ensure parser libraries, like nodes.rb, are loaded
|
48
|
+
Marshal.load(File.read(cache))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns treetop parser (recompiled as needed)
|
53
|
+
def self.parser
|
54
|
+
require 'treetop'
|
55
|
+
require 'piggly/parser/treetop_ruby19_patch'
|
56
|
+
require nodes_path
|
57
|
+
|
58
|
+
if File.stale?(parser_path, grammar_path)
|
59
|
+
Treetop::Compiler::GrammarCompiler.new.compile(grammar_path, parser_path)
|
60
|
+
end
|
61
|
+
|
62
|
+
require parser_path
|
63
|
+
::PigglyParser.new
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Piggly
|
2
|
+
|
3
|
+
#
|
4
|
+
# Collection of all Tags
|
5
|
+
#
|
6
|
+
class Profile
|
7
|
+
PATTERN = /WARNING: #{Config.trace_prefix} (#{Tag::PATTERN})(?: (.))?/
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Build a notice processor function that records each tag execution
|
12
|
+
def notice_processor
|
13
|
+
proc do |message|
|
14
|
+
if m = PATTERN.match(message)
|
15
|
+
ping(m.captures[0], m.captures[1])
|
16
|
+
else
|
17
|
+
STDERR.puts message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Register a source file (path) with its list of tags
|
23
|
+
def add(path, tags, cache = nil)
|
24
|
+
tags.each{|t| by_id[t.id] = t }
|
25
|
+
by_file[path] = tags
|
26
|
+
by_cache[cache] = tags if cache
|
27
|
+
end
|
28
|
+
|
29
|
+
# Each tag indexed by unique ID
|
30
|
+
def by_id
|
31
|
+
@by_id ||= Hash.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Each tag grouped by source file path
|
35
|
+
def by_file
|
36
|
+
@by_file ||= Hash.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Each tag grouped by FileCache
|
40
|
+
def by_cache
|
41
|
+
@by_cache ||= Hash.new
|
42
|
+
end
|
43
|
+
|
44
|
+
# Record the execution of a coverage tag
|
45
|
+
def ping(tag_id, value=nil)
|
46
|
+
if tag = by_id[tag_id]
|
47
|
+
tag.ping(value)
|
48
|
+
else
|
49
|
+
raise "No tag with id #{tag_id}, perhaps the proc was not compiled with Piggly::Installer.trace_proc, or it has been recompiled with new tag IDs."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Summarizes coverage for each type of tag (branch, block, loop)
|
54
|
+
def summary(file = nil)
|
55
|
+
summary = Hash.new{|h,k| h[k] = Hash.new }
|
56
|
+
|
57
|
+
if file
|
58
|
+
if by_file.include?(file)
|
59
|
+
grouped = by_file[file].group_by{|t| t.type }
|
60
|
+
else
|
61
|
+
grouped = {}
|
62
|
+
end
|
63
|
+
else
|
64
|
+
grouped = map.group_by{|t| t.type }
|
65
|
+
end
|
66
|
+
|
67
|
+
grouped.each do |type, ts|
|
68
|
+
summary[type][:count] = ts.size
|
69
|
+
summary[type][:percent] = ts.sum{|t| t.to_f } / ts.size
|
70
|
+
end
|
71
|
+
|
72
|
+
summary
|
73
|
+
end
|
74
|
+
|
75
|
+
# Resets each tag's coverage stats
|
76
|
+
def clear
|
77
|
+
by_id.values.each{|t| t.clear }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Write each tag's coverage stats to the disk cache
|
81
|
+
def store
|
82
|
+
by_cache.each{|cache, tags| cache[:tags] = tags }
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module Piggly
|
2
|
+
|
3
|
+
#
|
4
|
+
# Markup DSL
|
5
|
+
#
|
6
|
+
module HtmlTag
|
7
|
+
unless defined? HTML_REPLACE
|
8
|
+
HTML_REPLACE = { '&' => '&', '"' => '"', '>' => '>', '<' => '<' }
|
9
|
+
HTML_PATTERN = /[&"<>]/
|
10
|
+
end
|
11
|
+
|
12
|
+
def html(output = '')
|
13
|
+
begin
|
14
|
+
@htmltag_output, htmltag_output = output, @htmltag_output
|
15
|
+
# TODO: doctype
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
# restore
|
19
|
+
@htmltag_output = htmltag_output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def tag(name, content = nil, attributes = {})
|
24
|
+
if content.is_a?(Hash) and attributes.empty?
|
25
|
+
content, attributes = nil, content
|
26
|
+
end
|
27
|
+
|
28
|
+
attributes = attributes.inject('') do |string, pair|
|
29
|
+
k, v = pair
|
30
|
+
string << %[ #{k}="#{v}"]
|
31
|
+
end
|
32
|
+
|
33
|
+
if content.nil?
|
34
|
+
if block_given?
|
35
|
+
@htmltag_output << "<#{name}#{attributes}>"
|
36
|
+
yield
|
37
|
+
@htmltag_output << "</#{name}>"
|
38
|
+
else
|
39
|
+
@htmltag_output << "<#{name}#{attributes}/>"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
@htmltag_output << "<#{name}#{attributes}>#{content.to_s}</#{name}>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if ''.respond_to?(:fast_xs)
|
47
|
+
def e(string)
|
48
|
+
e.fast_xs
|
49
|
+
end
|
50
|
+
elsif ''.respond_to?(:to_xs)
|
51
|
+
def e(string)
|
52
|
+
e.to_xs
|
53
|
+
end
|
54
|
+
else
|
55
|
+
def e(string)
|
56
|
+
string.gsub(HTML_PATTERN) {|c| HTML_REPLACE[c] }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class HtmlReporter < Reporter
|
62
|
+
extend HtmlTag
|
63
|
+
|
64
|
+
def self.output(path, data, summary)
|
65
|
+
File.open(report_path(path, '.html'), 'w') do |f|
|
66
|
+
html(f) do
|
67
|
+
|
68
|
+
tag :html, :xmlns => 'http://www.w3.org/1999/xhtml' do
|
69
|
+
tag :head do
|
70
|
+
tag :title, "Code Coverage: #{File.basename(path)}"
|
71
|
+
tag :link, :rel => 'stylesheet', :type => 'text/css', :href => 'piggly.css'
|
72
|
+
end
|
73
|
+
|
74
|
+
tag :body do
|
75
|
+
table(path)
|
76
|
+
|
77
|
+
tag :br
|
78
|
+
tag :div, :class => 'listing' do
|
79
|
+
tag :table do
|
80
|
+
tag :tr do
|
81
|
+
tag :td, data.fetch('lines').to_a.map{|n| %[<a href="#L#{n}" id="L#{n}">#{n}</a>] }.join("\n"), :class => 'lines'
|
82
|
+
tag :td, data.fetch('html'), :class => 'code'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
toc(data.fetch('tags'))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.toc(tags)
|
96
|
+
todo = tags.reject{|t| t.complete? }
|
97
|
+
|
98
|
+
tag :div, :class => 'toc' do
|
99
|
+
tag :a, 'Index', :href => 'index.html'
|
100
|
+
|
101
|
+
unless todo.empty?
|
102
|
+
tag :ol do
|
103
|
+
todo.each do |t|
|
104
|
+
tag(:li, :class => t.type) { tag :a, t.description, :href => "#T#{t.id}" }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.timestamp
|
112
|
+
tag :div, "Generated by piggly #{Piggly::VERSION} at #{Time.now.strftime('%B %d, %Y %H:%M %Z')}", :class => 'timestamp'
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.table(*files)
|
116
|
+
tag :table, :class => 'summary sortable' do
|
117
|
+
tag :tr do
|
118
|
+
tag :th, 'File'
|
119
|
+
tag :th, 'Blocks'
|
120
|
+
tag :th, 'Loops'
|
121
|
+
tag :th, 'Branches'
|
122
|
+
tag :th, 'Block Coverage'
|
123
|
+
tag :th, 'Loop Coverage'
|
124
|
+
tag :th, 'Branch Coverage'
|
125
|
+
end
|
126
|
+
|
127
|
+
files.each_with_index do |name, index|
|
128
|
+
summary = Profile.summary(name)
|
129
|
+
row = index.modulo(2) == 0 ? 'even' : 'odd'
|
130
|
+
|
131
|
+
tag :tr, :class => row do
|
132
|
+
unless summary.include?(:block) or summary.include?(:loop) or summary.include?(:branch)
|
133
|
+
# PigglyParser couldn't parse this file
|
134
|
+
tag :td, File.basename(name), :class => 'file fail'
|
135
|
+
tag(:td, :class => 'count') { tag :span, -1, :style => 'display:none' }
|
136
|
+
tag(:td, :class => 'count') { tag :span, -1, :style => 'display:none' }
|
137
|
+
tag(:td, :class => 'count') { tag :span, -1, :style => 'display:none' }
|
138
|
+
tag(:td, :class => 'pct') { tag :span, -1, :style => 'display:none' }
|
139
|
+
tag(:td, :class => 'pct') { tag :span, -1, :style => 'display:none' }
|
140
|
+
tag(:td, :class => 'pct') { tag :span, -1, :style => 'display:none' }
|
141
|
+
else
|
142
|
+
tag(:td, :class => 'file') { tag :a, File.basename(name), :href => File.basename(name, '.*') + '.html' }
|
143
|
+
tag :td, (summary[:block][:count] || 0), :class => 'count'
|
144
|
+
tag :td, (summary[:loop][:count] || 0), :class => 'count'
|
145
|
+
tag :td, (summary[:branch][:count] || 0), :class => 'count'
|
146
|
+
tag(:td, :class => 'pct') { percent(summary[:block][:percent]) }
|
147
|
+
tag(:td, :class => 'pct') { percent(summary[:loop][:percent]) }
|
148
|
+
tag(:td, :class => 'pct') { percent(summary[:branch][:percent]) }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.percent(pct)
|
157
|
+
if pct
|
158
|
+
tag :table, :align => 'center' do
|
159
|
+
tag :tr do
|
160
|
+
|
161
|
+
tag :td, '%0.2f%% ' % pct, :class => 'num'
|
162
|
+
tag :td, :class => 'graph' do
|
163
|
+
if pct
|
164
|
+
tag :table, :align => 'right', :class => 'graph' do
|
165
|
+
tag :tr do
|
166
|
+
tag :td, :class => 'covered', :width => (pct/2.0).to_i
|
167
|
+
tag :td, :class => 'uncovered', :width => ((100-pct)/2.0).to_i
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
else
|
176
|
+
tag :span, -1, :style => 'display:none'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class Index < HtmlReporter
|
181
|
+
extend HtmlTag
|
182
|
+
|
183
|
+
def self.output(sources)
|
184
|
+
File.open(File.join(report_path, 'index.html'), 'w') do |f|
|
185
|
+
html(f) do
|
186
|
+
|
187
|
+
tag :html do
|
188
|
+
tag :head do
|
189
|
+
tag :title, 'Piggly PL/pgSQL Code Coverage'
|
190
|
+
tag :link, :rel => 'stylesheet', :type => 'text/css', :href => 'piggly.css'
|
191
|
+
tag :script, '<!-- -->', :type => 'text/javascript', :src => 'sortable.js'
|
192
|
+
end
|
193
|
+
|
194
|
+
tag :body do
|
195
|
+
table(*sources.sort)
|
196
|
+
tag :br
|
197
|
+
timestamp
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
* { margin: 0; padding: 0; }
|
2
|
+
|
3
|
+
body
|
4
|
+
{
|
5
|
+
color: #000;
|
6
|
+
background-color: #fff;
|
7
|
+
padding: 8px;
|
8
|
+
}
|
9
|
+
|
10
|
+
a img { border: 0; }
|
11
|
+
|
12
|
+
div.timestamp { clear: both; }
|
13
|
+
|
14
|
+
div.toc
|
15
|
+
{
|
16
|
+
width: 200px;
|
17
|
+
height: 150px;
|
18
|
+
border: 1px solid #c00;
|
19
|
+
background-color: #fee;
|
20
|
+
padding: 5px;
|
21
|
+
position: fixed;
|
22
|
+
overflow: auto;
|
23
|
+
bottom: 10px;
|
24
|
+
right: 10px;
|
25
|
+
font-family: "Tahoma", "Trebuchet MS", "Arial", sans-serif;
|
26
|
+
}
|
27
|
+
div.toc ol { margin-left: 1.6em; }
|
28
|
+
div.toc a { color: #c00; }
|
29
|
+
div.toc li { font-size: 75%; }
|
30
|
+
div.toc li.branch { }
|
31
|
+
div.toc li.block { }
|
32
|
+
div.toc li.loop { }
|
33
|
+
|
34
|
+
/* main container for line numbers and code */
|
35
|
+
div.listing
|
36
|
+
{
|
37
|
+
background-color: #f9f9f9;
|
38
|
+
border: 1px solid silver;
|
39
|
+
margin: 0 0 1.5em 0;
|
40
|
+
overflow: auto;
|
41
|
+
}
|
42
|
+
|
43
|
+
div.listing table { border-collapse: collapse; }
|
44
|
+
div.listing td.code
|
45
|
+
{
|
46
|
+
vertical-align: top;
|
47
|
+
padding: 2px 4px;
|
48
|
+
white-space: pre;
|
49
|
+
|
50
|
+
margin: 0;
|
51
|
+
width: 100%;
|
52
|
+
float: none;
|
53
|
+
clear: none;
|
54
|
+
overflow: visible;
|
55
|
+
|
56
|
+
color: #000;
|
57
|
+
font-family: "DejaVu Sans Mono", "Monaco", "Consolas", "Nimbus Mono L", "Courier New";
|
58
|
+
font-size: 9pt;
|
59
|
+
}
|
60
|
+
|
61
|
+
/* line numbers */
|
62
|
+
div.listing td.lines
|
63
|
+
{
|
64
|
+
vertical-align: top;
|
65
|
+
padding: 2px 4px;
|
66
|
+
white-space: pre;
|
67
|
+
|
68
|
+
text-align: right;
|
69
|
+
overflow: visible;
|
70
|
+
|
71
|
+
background-color: #def;
|
72
|
+
font-size: 9pt;
|
73
|
+
font-family: "DejaVu Sans Mono", "Monaco", "Consolas", "Nimbus Mono L", "Courier New";
|
74
|
+
}
|
75
|
+
div.listing td.lines a { color: grey; text-decoration: none; }
|
76
|
+
|
77
|
+
table.summary th { padding: 5px; border: 1px solid silver; font-weight: bold; font-size: 12px; background-color: #def; }
|
78
|
+
table.summary td { padding: 5px; font-size: 10pt; }
|
79
|
+
|
80
|
+
table.summary td.file { text-align: left; border: 0; }
|
81
|
+
table.summary td.fail { font-weight: bold; color: #f00; }
|
82
|
+
table.summary td.count { max-width: 50px; min-width: 50px; text-align: right; border: 1px solid silver; }
|
83
|
+
table.summary td.pct { max-width: 100px; min-width: 100px; text-align: right; border: 0; }
|
84
|
+
|
85
|
+
table.summary td.pct td.num { padding: 0; max-width: 50px; min-width: 50px; text-align: right; border: 0; }
|
86
|
+
table.summary td.pct td.graph { padding: 0; max-width: 50px; min-width: 50px; text-align: right; border: 0; }
|
87
|
+
|
88
|
+
table.graph td.uncovered { background-color: #669; border: 0px; padding: 0px; }
|
89
|
+
table.graph td.covered { background-color: #ccf; border: 0px; padding: 0px; }
|
90
|
+
|
91
|
+
table.summary
|
92
|
+
{
|
93
|
+
font-family: "Tahoma", "Trebuchet MS", "Arial", sans-serif;
|
94
|
+
width: 100%;
|
95
|
+
border-spacing: 0;
|
96
|
+
border-collapse: collapse;
|
97
|
+
}
|
98
|
+
|
99
|
+
table.summary td.pct table
|
100
|
+
{
|
101
|
+
font-size: 85%;
|
102
|
+
font-family: "Tahoma", "Trebuchet MS", "Arial", sans-serif;
|
103
|
+
padding: 0;
|
104
|
+
border-spacing: 0;
|
105
|
+
border-collapse: collapse;
|
106
|
+
}
|
107
|
+
|
108
|
+
table.graph
|
109
|
+
{
|
110
|
+
min-width: 50px;
|
111
|
+
max-width: 50px;
|
112
|
+
padding: 0px;
|
113
|
+
border: 1px solid #000;
|
114
|
+
border-spacing: 0px;
|
115
|
+
height: 10px;
|
116
|
+
}
|
117
|
+
|
118
|
+
/* SYNTAX HIGHLIGHTING */
|
119
|
+
|
120
|
+
/* identifier */
|
121
|
+
.tI { color: #666; }
|
122
|
+
|
123
|
+
/* data type */
|
124
|
+
.tD { color: #cc3; font-style: italic; }
|
125
|
+
|
126
|
+
/* keyword */
|
127
|
+
.tK { color: #f30; }
|
128
|
+
|
129
|
+
/* comment */
|
130
|
+
.tC { color: #66f; font-style: italic; }
|
131
|
+
|
132
|
+
/* sql statement */
|
133
|
+
.tQ { color: #6c3; font-style: italic; }
|
134
|
+
|
135
|
+
/* string literal */
|
136
|
+
.tS { color: #390; }
|
137
|
+
|
138
|
+
/* label */
|
139
|
+
.tL { color: #630; font-style: italic; }
|
140
|
+
|
141
|
+
/* dollar quote marker */
|
142
|
+
.tM { }
|
143
|
+
|
144
|
+
/* tagged code blocks */
|
145
|
+
.b { display: block; width: 100%; margin: 0; padding: 0; }
|
146
|
+
.i { display: inline; }
|
147
|
+
|
148
|
+
/* line with incomplete coverage */
|
149
|
+
.lU
|
150
|
+
{
|
151
|
+
display: block;
|
152
|
+
background: #fdd;
|
153
|
+
|
154
|
+
margin: 0px;
|
155
|
+
padding: 0px;
|
156
|
+
border: none;
|
157
|
+
border-left: 2px solid #f00;
|
158
|
+
}
|
159
|
+
|
160
|
+
/* block execution: yes, no */
|
161
|
+
.c0 { font-weight: bold; }
|
162
|
+
.c1 { }
|
163
|
+
|
164
|
+
/* loop coverage */
|
165
|
+
.l0000 { font-weight: bold; }
|
166
|
+
.l0001 { font-weight: bold; }
|
167
|
+
.l0010 { font-weight: bold; }
|
168
|
+
.l0100 { font-weight: bold; }
|
169
|
+
.l0011 { font-weight: bold; }
|
170
|
+
.l0101 { font-weight: bold; }
|
171
|
+
.l0110 { font-weight: bold; }
|
172
|
+
.l0111 { font-weight: bold; }
|
173
|
+
.l1000 { font-weight: bold; }
|
174
|
+
.l1001 { font-weight: bold; }
|
175
|
+
.l1010 { font-weight: bold; }
|
176
|
+
.l1100 { font-weight: bold; }
|
177
|
+
.l1011 { font-weight: bold; }
|
178
|
+
.l1101 { font-weight: bold; }
|
179
|
+
.l1110 { font-weight: bold; }
|
180
|
+
.l1111 { }
|
181
|
+
|
182
|
+
/* branch decisions: true, false */
|
183
|
+
.b00 { font-weight: bold; } /* never evaluated */
|
184
|
+
.b01 { font-weight: bold; color: #060; } /* never evaluates true */
|
185
|
+
.b10 { font-weight: bold; color: #900; } /* never evaluates false */
|
186
|
+
.b11 { }
|
187
|
+
|