piggly 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +163 -0
  3. data/Rakefile +29 -15
  4. data/bin/piggly +4 -244
  5. data/lib/piggly.rb +19 -17
  6. data/lib/piggly/command.rb +9 -0
  7. data/lib/piggly/command/base.rb +148 -0
  8. data/lib/piggly/command/report.rb +162 -0
  9. data/lib/piggly/command/test.rb +157 -0
  10. data/lib/piggly/command/trace.rb +90 -0
  11. data/lib/piggly/command/untrace.rb +78 -0
  12. data/lib/piggly/compiler.rb +7 -5
  13. data/lib/piggly/compiler/cache_dir.rb +119 -0
  14. data/lib/piggly/compiler/coverage_report.rb +63 -0
  15. data/lib/piggly/compiler/trace_compiler.rb +105 -0
  16. data/lib/piggly/config.rb +47 -22
  17. data/lib/piggly/dumper.rb +9 -0
  18. data/lib/piggly/dumper/index.rb +121 -0
  19. data/lib/piggly/dumper/qualified_name.rb +36 -0
  20. data/lib/piggly/dumper/qualified_type.rb +81 -0
  21. data/lib/piggly/dumper/reified_procedure.rb +142 -0
  22. data/lib/piggly/dumper/skeleton_procedure.rb +102 -0
  23. data/lib/piggly/installer.rb +84 -42
  24. data/lib/piggly/parser.rb +43 -49
  25. data/lib/piggly/parser/grammar.tt +289 -313
  26. data/lib/piggly/parser/nodes.rb +270 -211
  27. data/lib/piggly/parser/traversal.rb +35 -33
  28. data/lib/piggly/parser/treetop_ruby19_patch.rb +1 -1
  29. data/lib/piggly/profile.rb +81 -60
  30. data/lib/piggly/reporter.rb +5 -18
  31. data/lib/piggly/reporter/base.rb +103 -0
  32. data/lib/piggly/reporter/html_dsl.rb +63 -0
  33. data/lib/piggly/reporter/index.rb +108 -0
  34. data/lib/piggly/reporter/procedure.rb +104 -0
  35. data/lib/piggly/reporter/resources/highlight.js +21 -0
  36. data/lib/piggly/reporter/{piggly.css → resources/piggly.css} +52 -12
  37. data/lib/piggly/reporter/{sortable.js → resources/sortable.js} +0 -0
  38. data/lib/piggly/tags.rb +280 -0
  39. data/lib/piggly/task.rb +191 -40
  40. data/lib/piggly/util.rb +8 -27
  41. data/lib/piggly/util/blankslate.rb +114 -0
  42. data/lib/piggly/util/cacheable.rb +19 -0
  43. data/lib/piggly/util/enumerable.rb +44 -0
  44. data/lib/piggly/util/file.rb +17 -0
  45. data/lib/piggly/util/process_queue.rb +96 -0
  46. data/lib/piggly/util/thunk.rb +39 -0
  47. data/lib/piggly/version.rb +8 -8
  48. data/spec/examples/compiler/cacheable_spec.rb +190 -0
  49. data/spec/examples/compiler/report_spec.rb +25 -0
  50. data/spec/{compiler → examples/compiler}/trace_spec.rb +7 -57
  51. data/spec/examples/config_spec.rb +61 -0
  52. data/spec/examples/dumper/index_spec.rb +197 -0
  53. data/spec/examples/dumper/procedure_spec.rb +116 -0
  54. data/spec/{grammar → examples/grammar}/expression_spec.rb +60 -60
  55. data/spec/{grammar → examples/grammar}/statements/assignment_spec.rb +15 -15
  56. data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
  57. data/spec/{grammar → examples/grammar}/statements/exception_spec.rb +10 -10
  58. data/spec/{grammar → examples/grammar}/statements/if_spec.rb +47 -34
  59. data/spec/{grammar → examples/grammar}/statements/loop_spec.rb +5 -5
  60. data/spec/{grammar → examples/grammar}/statements/sql_spec.rb +11 -11
  61. data/spec/{grammar → examples/grammar}/tokens/comment_spec.rb +11 -11
  62. data/spec/{grammar → examples/grammar}/tokens/datatype_spec.rb +14 -8
  63. data/spec/{grammar → examples/grammar}/tokens/identifier_spec.rb +26 -10
  64. data/spec/{grammar → examples/grammar}/tokens/keyword_spec.rb +5 -5
  65. data/spec/{grammar → examples/grammar}/tokens/label_spec.rb +7 -7
  66. data/spec/{grammar → examples/grammar}/tokens/literal_spec.rb +1 -1
  67. data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
  68. data/spec/{grammar → examples/grammar}/tokens/number_spec.rb +1 -1
  69. data/spec/{grammar → examples/grammar}/tokens/sqlkeywords_spec.rb +1 -1
  70. data/spec/{grammar → examples/grammar}/tokens/string_spec.rb +9 -9
  71. data/spec/{grammar → examples/grammar}/tokens/whitespace_spec.rb +1 -1
  72. data/spec/examples/installer_spec.rb +59 -0
  73. data/spec/examples/parser/nodes_spec.rb +73 -0
  74. data/spec/examples/parser/traversal_spec.rb +14 -0
  75. data/spec/examples/parser_spec.rb +115 -0
  76. data/spec/examples/profile_spec.rb +153 -0
  77. data/spec/{reporter/html_spec.rb → examples/reporter/html/dsl_spec.rb} +0 -0
  78. data/spec/examples/reporter/html/index_spec.rb +0 -0
  79. data/spec/examples/reporter/html_spec.rb +1 -0
  80. data/spec/examples/reporter_spec.rb +0 -0
  81. data/spec/{compiler → examples}/tags_spec.rb +10 -10
  82. data/spec/examples/task_spec.rb +0 -0
  83. data/spec/examples/util/cacheable_spec.rb +41 -0
  84. data/spec/examples/util/enumerable_spec.rb +64 -0
  85. data/spec/examples/util/file_spec.rb +40 -0
  86. data/spec/examples/util/process_queue_spec.rb +16 -0
  87. data/spec/examples/util/thunk_spec.rb +58 -0
  88. data/spec/examples/version_spec.rb +0 -0
  89. data/spec/issues/007_spec.rb +25 -0
  90. data/spec/issues/008_spec.rb +73 -0
  91. data/spec/issues/018_spec.rb +25 -0
  92. data/spec/spec_helper.rb +253 -9
  93. metadata +136 -93
  94. data/README.markdown +0 -116
  95. data/lib/piggly/compiler/cache.rb +0 -151
  96. data/lib/piggly/compiler/pretty.rb +0 -67
  97. data/lib/piggly/compiler/queue.rb +0 -46
  98. data/lib/piggly/compiler/tags.rb +0 -244
  99. data/lib/piggly/compiler/trace.rb +0 -91
  100. data/lib/piggly/filecache.rb +0 -40
  101. data/lib/piggly/parser/parser.rb +0 -11794
  102. data/lib/piggly/reporter/html.rb +0 -207
  103. data/spec/compiler/cache_spec.rb +0 -9
  104. data/spec/compiler/pretty_spec.rb +0 -9
  105. data/spec/compiler/queue_spec.rb +0 -3
  106. data/spec/compiler/rewrite_spec.rb +0 -3
  107. data/spec/config_spec.rb +0 -58
  108. data/spec/filecache_spec.rb +0 -70
  109. data/spec/fixtures/snippets.sql +0 -158
  110. data/spec/grammar/tokens/lval_spec.rb +0 -50
  111. data/spec/parser_spec.rb +0 -8
  112. data/spec/profile_spec.rb +0 -5
@@ -1,48 +1,50 @@
1
1
  module Piggly
2
+ module Parser
2
3
 
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) }
4
+ #
5
+ # Routines for traversing a tree; assumes base class defines elements
6
+ # as a method that returns a list of child nodes
7
+ #
8
+ module Traversal
9
+ def inject(init) # :yields: NodeClass => init
10
+ if elements
11
+ elements.inject(yield(init, self)) do |state, e|
12
+ e.inject(state){|succ, n| yield(succ, n) }
13
+ end
14
+ else
15
+ yield(init, self)
12
16
  end
13
- else
14
- yield(init, self)
15
17
  end
16
- end
17
18
 
18
- def count # :yields: NodeClass => boolean
19
- fold_down(0){|sum, e| yield(e) ? sum + 1 : sum }
20
- end
19
+ def count # :yields: NodeClass => boolean
20
+ inject(0){|sum, e| yield(e) ? sum + 1 : sum }
21
+ end
21
22
 
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
23
+ def find # :yields: NodeClass => boolean
24
+ found = false
25
+ catch :done do
26
+ inject(nil) do |_,e|
27
+ if yield(e)
28
+ found = e
29
+ throw :done
30
+ end
29
31
  end
30
32
  end
33
+ found
31
34
  end
32
- found
33
- end
34
35
 
35
- def select # :yields: NodeClass => boolean
36
- fold_down([]){|list,e| yield(e) ? list << e : list }
37
- end
36
+ def select # :yields: NodeClass => boolean
37
+ inject([]){|list,e| yield(e) ? list << e : list }
38
+ end
38
39
 
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 }
40
+ def flatten # :yields: NodeClass
41
+ if block_given?
42
+ inject([]){|list,e| list << yield(e) }
43
+ else
44
+ inject([]){|list,e| list << e }
45
+ end
44
46
  end
45
47
  end
46
- end
47
48
 
49
+ end
48
50
  end
@@ -10,7 +10,7 @@ module Treetop
10
10
  super
11
11
  end
12
12
  end
13
- end if RUBY_VERSION > '1.9.0'
13
+ end if RUBY_VERSION >= '1.9.0'
14
14
 
15
15
  end
16
16
  end
@@ -4,84 +4,105 @@ module Piggly
4
4
  # Collection of all Tags
5
5
  #
6
6
  class Profile
7
- PATTERN = /WARNING: #{Config.trace_prefix} (#{Tag::PATTERN})(?: (.))?/
8
7
 
9
- class << self
8
+ def initialize
9
+ @by_id = {}
10
+ @by_cache = {}
11
+ @by_procedure = {}
12
+ end
13
+
14
+ # Register a procedure and its list of tags
15
+ def add(procedure, tags, cache = nil)
16
+ tags.each{|t| @by_id[t.id] = t }
17
+ @by_cache[cache] = tags unless cache.nil?
18
+ @by_procedure[procedure.oid] = tags
19
+ end
10
20
 
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])
21
+ def [](object)
22
+ case object
23
+ when String
24
+ @by_id[object] or
25
+ raise "No tag with id #{object}"
26
+ when Dumper::ReifiedProcedure,
27
+ Dumper::SkeletonProcedure
28
+ @by_procedure[object.oid] or
29
+ raise "No tags for procedure #{object.signature}"
30
+ end
31
+ end
32
+
33
+ # Record the execution of a coverage tag
34
+ def ping(tag_id, value=nil)
35
+ self[tag_id].ping(value)
36
+ end
37
+
38
+ # Summarizes coverage for each type of tag (branch, block, loop)
39
+ # @return [Hash<Symbol, Hash[:count => Integer, :percent => Float]>]
40
+ def summary(procedure = nil)
41
+ tags =
42
+ if procedure
43
+ if @by_procedure.include?(procedure.oid)
44
+ @by_procedure[procedure.oid]
16
45
  else
17
- STDERR.puts message
46
+ []
18
47
  end
48
+ else
49
+ @by_id.values
19
50
  end
20
- end
21
51
 
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
52
+ grouped = Util::Enumerable.group_by(tags){|x| x.type }
28
53
 
29
- # Each tag indexed by unique ID
30
- def by_id
31
- @by_id ||= Hash.new
54
+ summary = Hash.new{|h,k| h[k] = Hash.new }
55
+ grouped.each do |type, ts|
56
+ summary[type][:count] = ts.size
57
+ summary[type][:percent] = Util::Enumerable.sum(ts){|x| x.to_f } / ts.size
32
58
  end
33
59
 
34
- # Each tag grouped by source file path
35
- def by_file
36
- @by_file ||= Hash.new
37
- end
60
+ summary
61
+ end
38
62
 
39
- # Each tag grouped by FileCache
40
- def by_cache
41
- @by_cache ||= Hash.new
42
- end
63
+ # Resets each tag's coverage stats
64
+ def clear
65
+ @by_id.values.each{|x| x.clear }
66
+ end
43
67
 
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
68
+ # Write coverage stats to the disk cache
69
+ def store
70
+ @by_cache.each{|cache, tags| cache[:tags] = tags }
71
+ end
52
72
 
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 }
73
+ def empty?(tags)
74
+ tags.all?{|t| t.to_f.zero? }
75
+ end
56
76
 
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
77
+ # @return [String]
78
+ def difference(procedure, tags)
79
+ current = Util::Enumerable.group_by(@by_procedure[procedure.oid]){|x| x.type }
80
+ previous = Util::Enumerable.group_by(tags){|x| x.type }
66
81
 
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
82
+ current.default = []
83
+ previous.default = []
71
84
 
72
- summary
73
- end
85
+ (current.keys | previous.keys).map do |type|
86
+ pct = Util::Enumerable.sum(current[type]){|x| x.to_f } / current[type].size -
87
+ Util::Enumerable.sum(previous[type]){|x| x.to_f } / previous[type].size
74
88
 
75
- # Resets each tag's coverage stats
76
- def clear
77
- by_id.values.each{|t| t.clear }
78
- end
89
+ "#{"%+0.1f" % pct}% #{type}"
90
+ end.join(", ")
91
+ end
79
92
 
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
93
+ # Build a notice processor function that records each tag execution
94
+ # @return [Proc]
95
+ def notice_processor(config, stderr = $stderr)
96
+ pattern = /#{config.trace_prefix} (#{Tags::AbstractTag::PATTERN})(?: (.))?/
84
97
 
98
+ lambda do |message|
99
+ if m = pattern.match(message)
100
+ ping(m.captures[0], m.captures[1])
101
+ else
102
+ stderr.puts(message)
103
+ end
104
+ end
85
105
  end
86
106
  end
107
+
87
108
  end
@@ -1,21 +1,8 @@
1
1
  module Piggly
2
- class Reporter
3
-
4
- def self.report_path(file=nil, ext=nil)
5
- Piggly::Config.mkpath(Config.report_root, ext ? File.basename(file).sub(/\.[^.]+$/i, ext) : file)
6
- end
7
-
8
- # Copy each file to Config.report_root
9
- def self.install(*files)
10
- files.each do |file|
11
- src = File.join(File.dirname(__FILE__), 'reporter', file)
12
- dst = report_path(file)
13
-
14
- File.open(dst, 'w') {|f| f.write File.read(src) }
15
- end
16
- end
17
-
2
+ module Reporter
3
+ autoload :HtmlDsl, "piggly/reporter/html_dsl"
4
+ autoload :Base, "piggly/reporter/base"
5
+ autoload :Index, "piggly/reporter/index"
6
+ autoload :Procedure, "piggly/reporter/procedure"
18
7
  end
19
8
  end
20
-
21
- require File.join(File.dirname(__FILE__), *%w[reporter html])
@@ -0,0 +1,103 @@
1
+ module Piggly
2
+ module Reporter
3
+
4
+ class Base
5
+ include HtmlDsl
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ # Copy each file to @config.report_root
12
+ def install(*files)
13
+ files.each do |name|
14
+ src = File.join(File.dirname(__FILE__), name)
15
+ dst = report_path(name)
16
+
17
+ File.open(dst, "w"){|io| io.write(File.read(src)) }
18
+ end
19
+ end
20
+
21
+ def report_path(file=nil, ext=nil)
22
+ unless file.nil?
23
+ # Remove the original extension from +file+ and add given extension
24
+ @config.mkpath(@config.report_root, ext ?
25
+ File.basename(file, ".*") + ext :
26
+ File.basename(file))
27
+ else
28
+ @config.mkpath(@config.report_root)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def aggregate(label, summary)
35
+ tag :p, label, :class => "summary"
36
+ tag :table, :class => "summary sortable" do
37
+ tag :tr do
38
+ tag :th, "Blocks"
39
+ tag :th, "Loops"
40
+ tag :th, "Branches"
41
+ tag :th, "Block Coverage"
42
+ tag :th, "Loop Coverage"
43
+ tag :th, "Branch Coverage"
44
+ end
45
+
46
+ tag :tr, :class => "even" do
47
+ unless summary.include?(:block) or summary.include?(:loop) or summary.include?(:branch)
48
+ # Parser couldn't parse this file
49
+ tag(:td, :class => "count") { tag :span, -1, :style => "display:none" }
50
+ tag(:td, :class => "count") { tag :span, -1, :style => "display:none" }
51
+ tag(:td, :class => "count") { tag :span, -1, :style => "display:none" }
52
+ tag(:td, :class => "pct") { tag :span, -1, :style => "display:none" }
53
+ tag(:td, :class => "pct") { tag :span, -1, :style => "display:none" }
54
+ tag(:td, :class => "pct") { tag :span, -1, :style => "display:none" }
55
+ else
56
+ tag(:td, (summary[:block][:count] || 0), :class => "count")
57
+ tag(:td, (summary[:loop][:count] || 0), :class => "count")
58
+ tag(:td, (summary[:branch][:count] || 0), :class => "count")
59
+ tag(:td, :class => "pct") { percent(summary[:block][:percent]) }
60
+ tag(:td, :class => "pct") { percent(summary[:loop][:percent]) }
61
+ tag(:td, :class => "pct") { percent(summary[:branch][:percent]) }
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def percent(pct)
68
+ if pct
69
+ tag :table, :align => "center" do
70
+ tag :tr do
71
+ tag :td, "%0.2f%%&nbsp;" % pct, :class => "num"
72
+
73
+ style =
74
+ case pct.to_f
75
+ when 0...50; "low"
76
+ when 0...100; "mid"
77
+ else "high"
78
+ end
79
+
80
+ tag :td, :class => "graph" do
81
+ if pct
82
+ tag :table, :align => "right", :class => "graph #{style}" do
83
+ tag :tr do
84
+ tag :td, :class => "covered", :width => (pct/2.0).to_i
85
+ tag :td, :class => "uncovered", :width => ((100-pct)/2.0).to_i
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ else
93
+ tag :span, -1, :style => "display:none"
94
+ end
95
+ end
96
+
97
+ def timestamp
98
+ tag :div, "Generated by piggly #{Piggly::VERSION} at #{Time.now.strftime("%B %d, %Y %H:%M %Z")}", :class => "timestamp"
99
+ end
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,63 @@
1
+ module Piggly
2
+ module Reporter
3
+
4
+ #
5
+ # Markup DSL
6
+ #
7
+ module HtmlDsl
8
+ unless defined? HTML_REPLACE
9
+ HTML_REPLACE = { "&" => "&amp;", '"' => "&quot;", ">" => "&gt;", "<" => "&lt;" }
10
+ HTML_PATTERN = /[&"<>]/
11
+ end
12
+
13
+ def html(output = "")
14
+ begin
15
+ @htmltag_output, htmltag_output = output, @htmltag_output
16
+ # @todo: doctype
17
+ yield
18
+ ensure
19
+ # restore
20
+ @htmltag_output = htmltag_output
21
+ end
22
+ end
23
+
24
+ def tag(name, content = nil, attributes = {})
25
+ if content.is_a?(Hash) and attributes.empty?
26
+ content, attributes = nil, content
27
+ end
28
+
29
+ attributes = attributes.inject("") do |string, pair|
30
+ k, v = pair
31
+ string << %[ #{k}="#{v}"]
32
+ end
33
+
34
+ if content.nil?
35
+ if block_given?
36
+ @htmltag_output << "<#{name}#{attributes}>"
37
+ yield
38
+ @htmltag_output << "</#{name}>"
39
+ else
40
+ @htmltag_output << "<#{name}#{attributes}/>"
41
+ end
42
+ else
43
+ @htmltag_output << "<#{name}#{attributes}>#{content.to_s}</#{name}>"
44
+ end
45
+ end
46
+
47
+ if "".respond_to?(:fast_xs)
48
+ def e(string)
49
+ string.fast_xs
50
+ end
51
+ elsif "".respond_to?(:to_xs)
52
+ def e(string)
53
+ string.to_xs
54
+ end
55
+ else
56
+ def e(string)
57
+ string.gsub(HTML_PATTERN) {|c| HTML_REPLACE[c] }
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end