piggly 1.2.1 → 2.0.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.
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