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,18 +1,20 @@
1
- unless defined?(PIGGLY_ROOT)
2
- PIGGLY_ROOT = File.join(File.dirname(__FILE__), 'piggly')
3
- end
4
-
5
- require 'fileutils'
6
- require 'digest/md5'
7
- require File.join(PIGGLY_ROOT, 'version')
8
- require File.join(PIGGLY_ROOT, 'config')
9
- require File.join(PIGGLY_ROOT, 'filecache')
10
- require File.join(PIGGLY_ROOT, 'compiler')
11
- require File.join(PIGGLY_ROOT, 'parser')
12
- require File.join(PIGGLY_ROOT, 'profile')
13
- require File.join(PIGGLY_ROOT, 'installer')
14
- require File.join(PIGGLY_ROOT, 'reporter')
15
- require File.join(PIGGLY_ROOT, 'util')
1
+ require "erb"
2
+ require "yaml"
3
+ require "optparse"
4
+ require "fileutils"
5
+ require "digest/md5"
6
+ require "set"
16
7
 
17
- # used to generate MD5 tags for AST nodes
18
- $PIGGLY_GENTAG = 0
8
+ module Piggly
9
+ autoload :VERSION, "piggly/version"
10
+ autoload :Config, "piggly/config"
11
+ autoload :Command, "piggly/command"
12
+ autoload :Compiler, "piggly/compiler"
13
+ autoload :Dumper, "piggly/dumper"
14
+ autoload :Parser, "piggly/parser"
15
+ autoload :Profile, "piggly/profile"
16
+ autoload :Installer, "piggly/installer"
17
+ autoload :Reporter, "piggly/reporter"
18
+ autoload :Tags, "piggly/tags"
19
+ autoload :Util, "piggly/util"
20
+ end
@@ -0,0 +1,9 @@
1
+ module Piggly
2
+ module Command
3
+ autoload :Base, "piggly/command/base"
4
+ autoload :Report, "piggly/command/report"
5
+ autoload :Test, "piggly/command/test"
6
+ autoload :Trace, "piggly/command/trace"
7
+ autoload :Untrace, "piggly/command/untrace"
8
+ end
9
+ end
@@ -0,0 +1,148 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ class Base
5
+ end
6
+
7
+ class << Base
8
+
9
+ def main(argv)
10
+ cmd, argv = command(argv)
11
+
12
+ if cmd.nil?
13
+ abort "usage: #{$0} {test|report|trace|untrace} --help"
14
+ else
15
+ cmd.main(argv)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # @return [(Class, Array<String>)]
22
+ def command(argv)
23
+ return if argv.empty?
24
+ head, *tail = argv
25
+
26
+ case head.downcase
27
+ when "report"; [Report, tail]
28
+ when "test"; [Test, tail]
29
+ when "trace"; [Trace, tail]
30
+ when "untrace"; [Untrace, tail]
31
+ end
32
+ end
33
+
34
+ # @return [PGconn]
35
+ def connect(config)
36
+ require "pg"
37
+ require "erb"
38
+
39
+ files = Array(config.database_yml ||
40
+ %w(piggly/database.yml
41
+ config/database.yml
42
+ piggly/database.json
43
+ config/database.json))
44
+
45
+ path = files.find{|x| File.exists?(x) } or
46
+ raise "No database config files found: #{files.join(", ")}"
47
+
48
+ specs =
49
+ if File.extname(path) == ".json"
50
+ require "json"
51
+ JSON.load(ERB.new(IO.read(path)).result)
52
+ else
53
+ require "yaml"
54
+ YAML.load(ERB.new(IO.read(path)).result)
55
+ end
56
+
57
+ spec = (specs.is_a?(Hash) and specs[config.connection_name]) or
58
+ raise "Database '#{config.connection_name}' is not configured in #{path}"
59
+
60
+ PGconn.connect(spec["host"], spec["port"], nil, nil,
61
+ spec["database"], spec["username"], spec["password"])
62
+ end
63
+
64
+ # @return [Enumerable<SkeletonProcedure>]
65
+ def filter(config, index)
66
+ if config.filters.empty?
67
+ index.procedures
68
+ else
69
+ head, _ = config.filters
70
+
71
+ start =
72
+ case head.first
73
+ when :+; []
74
+ when :-; index.procedures
75
+ end
76
+
77
+ config.filters.inject(start) do |s, pair|
78
+ case pair.first
79
+ when :+
80
+ s | index.procedures.select(&pair.last)
81
+ when :-
82
+ s.reject(&pair.last)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def o_accumulate(config)
89
+ lambda{|x| config.accumulate = x }
90
+ end
91
+
92
+ def o_cache_root(config)
93
+ lambda{|x| config.cache_root = x }
94
+ end
95
+
96
+ def o_report_root(config)
97
+ lambda{|x| config.report_root = x }
98
+ end
99
+
100
+ def o_include_paths(config)
101
+ lambda{|x| config.include_paths.concat(x.split(":")) }
102
+ end
103
+
104
+ def o_database_yml(config)
105
+ lambda{|x| config.database_yml = x }
106
+ end
107
+
108
+ def o_connection_name(config)
109
+ lambda{|x| config.connection_name = x }
110
+ end
111
+
112
+ def o_version(config)
113
+ lambda { puts "piggly #{VERSION} #{VERSION::RELEASE_DATE}"; exit! }
114
+ end
115
+
116
+ def o_dry_run(config)
117
+ lambda { config.dry_run = true }
118
+ end
119
+
120
+ def o_select(config)
121
+ lambda do |x|
122
+ filter =
123
+ if m = x.match(%r{^/([^/]+)/$})
124
+ lambda{|p| p.name.to_s.match(m.captures.first) }
125
+ else
126
+ lambda{|p| p.name.to_s === x }
127
+ end
128
+
129
+ config.filters << [:+, filter]
130
+ end
131
+ end
132
+
133
+ def o_reject(config)
134
+ lambda do |x|
135
+ filter =
136
+ if m = x.match(%r{^/([^/]+)/$})
137
+ lambda{|p| p.name.to_s.match(m.captures.first) }
138
+ else
139
+ lambda{|p| p.name.to_s === x }
140
+ end
141
+
142
+ config.filters << [:-, filter]
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,162 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ #
5
+ # This command reads a given file (or STDIN) which is expected to contain messages like the
6
+ # pattern Profile::PATTERN, which is probbaly "WARNING: PIGGLY 0123456789abcdef".
7
+ #
8
+ # Lines in the input that match this pattern are profiled and used to generate a report
9
+ #
10
+ class Report < Base
11
+ end
12
+
13
+ class << Report
14
+ def main(argv)
15
+ require "pp"
16
+ io, config = configure(argv)
17
+
18
+ profile = Profile.new
19
+ index = Dumper::Index.new(config)
20
+
21
+ procedures = filter(config, index)
22
+
23
+ if procedures.empty?
24
+ if filters.empty?
25
+ abort "no stored procedures in the cache"
26
+ else
27
+ abort "no stored procedures in the cache matched your criteria"
28
+ end
29
+ elsif config.dry_run?
30
+ puts procedures.map{|p| p.signature }
31
+ exit 0
32
+ end
33
+
34
+ profile_procedures(config, procedures, profile)
35
+ clear_coverage(config, profile)
36
+
37
+ read_profile(config, io, profile)
38
+ store_coverage(profile)
39
+
40
+ create_index(config, index, procedures, profile)
41
+ create_reports(config, procedures, profile)
42
+ end
43
+
44
+ # Adds the given procedures to Profile
45
+ #
46
+ def profile_procedures(config, procedures, profile)
47
+ # register each procedure in the Profile
48
+ compiler = Compiler::TraceCompiler.new(config)
49
+ procedures.each do |p|
50
+ result = compiler.compile(p)
51
+ profile.add(p, result[:tags], result)
52
+ end
53
+ end
54
+
55
+ # Clear coverage after procedures have been loaded
56
+ #
57
+ def clear_coverage(config, profile)
58
+ unless config.accumulate?
59
+ puts "clearing previous coverage"
60
+ profile.clear
61
+ end
62
+ end
63
+
64
+ # Reads +io+ for lines matching Profile::PATTERN and records coverage
65
+ #
66
+ def read_profile(config, io, profile)
67
+ np = profile.notice_processor(config)
68
+ io.each{|line| np.call(line) }
69
+ end
70
+
71
+ # Store the coverage Profile on disk
72
+ #
73
+ def store_coverage(profile)
74
+ puts "storing coverage profile"
75
+ profile.store
76
+ end
77
+
78
+ # Create the report's index.html
79
+ #
80
+ def create_index(config, index, procedures, profile)
81
+ puts "creating index"
82
+ reporter = Reporter::Index.new(config, profile)
83
+ reporter.install("resources/piggly.css", "resources/sortable.js", "resources/highlight.js")
84
+ reporter.report(procedures, index)
85
+ end
86
+
87
+ # Create each procedures' HTML report page
88
+ #
89
+ def create_reports(config, procedures, profile)
90
+ puts "creating reports"
91
+ queue = Util::ProcessQueue.new
92
+
93
+ compiler = Compiler::TraceCompiler.new(config)
94
+ reporter = Reporter::Procedure.new(config, profile)
95
+
96
+ Parser.parser
97
+
98
+ procedures.each do |p|
99
+ queue.add do
100
+ unless compiler.stale?(p)
101
+ data = compiler.compile(p)
102
+ path = reporter.report_path(p.source_path(config), ".html")
103
+
104
+ unless profile.empty?(data[:tags])
105
+ changes = ": #{profile.difference(p, data[:tags])}"
106
+ end
107
+
108
+ puts "reporting coverage for #{p.name}#{changes}"
109
+ # pp data[:tags]
110
+ # pp profile[p]
111
+ # puts
112
+
113
+ reporter.report(p)
114
+ end
115
+ end
116
+ end
117
+
118
+ queue.execute
119
+ end
120
+
121
+ def configure(argv, config = Config.new)
122
+ io = $stdin
123
+ p = OptionParser.new do |o|
124
+ o.on("-t", "--dry-run", "only print the names of matching procedures", &o_dry_run(config))
125
+ o.on("-s", "--select PATTERN", "select procedures matching PATTERN", &o_select(config))
126
+ o.on("-r", "--reject PATTERN", "ignore procedures matching PATTERN", &o_reject(config))
127
+ o.on("-c", "--cache-root PATH", "local cache directory", &o_cache_root(config))
128
+ o.on("-o", "--report-root PATH", "report output directory", &o_report_root(config))
129
+ o.on("-a", "--accumulate", "accumulate data from the previous run", &o_accumulate(config))
130
+ o.on("-V", "--version", "show version", &o_version(config))
131
+ o.on("-h", "--help", "show this message") { abort o.to_s }
132
+ o.on("-f", "--input PATH", "read trace messages from PATH") do |path|
133
+ io = if path == "-"
134
+ $stdin
135
+ else
136
+ File.open(path, "rb")
137
+ end
138
+ end
139
+ end
140
+
141
+ begin
142
+ p.parse! argv
143
+
144
+ if io.eql?($stdin) and $stdin.tty?
145
+ raise OptionParser::MissingArgument,
146
+ "stdin must be a pipe, or use --input PATH"
147
+ end
148
+
149
+ return io, config
150
+ rescue OptionParser::InvalidOption,
151
+ OptionParser::InvalidArgument,
152
+ OptionParser::MissingArgument
153
+ puts p
154
+ puts
155
+ puts $!
156
+ exit! 1
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,157 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ #
5
+ # This command handles all the setup and teardown for running Ruby tests, that can
6
+ # otherwise be accomplished in a more manual fashion, using the other commands. It
7
+ # assumes that the test files will automatically establish a connection to the correct
8
+ # database when they are loaded (this is the case for Rails).
9
+ #
10
+ module Test
11
+ class << self
12
+
13
+ def main(argv)
14
+ benchmark do
15
+ tests, filters = parse_options(argv)
16
+ profile = Profile.new
17
+
18
+ # don't let rspec get these when loading spec files
19
+ ARGV.clear
20
+
21
+ load_tests(tests)
22
+ Command.connect_to_database
23
+
24
+ procedures = dump_procedures(filters)
25
+
26
+ if procedures.empty?
27
+ abort "No stored procedures in the database#{' matched your criteria' if filters.any?}"
28
+ end
29
+
30
+ result =
31
+ begin
32
+ trace(procedures)
33
+ clear_coverage
34
+ execute_tests
35
+ ensure
36
+ untrace(procedures)
37
+ end
38
+
39
+ create_index(procedures)
40
+ create_reports(procedures)
41
+ store_coverage
42
+
43
+ exit! result # avoid running tests again
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def benchmark
50
+ start = Time.now
51
+ value = yield
52
+ puts " > Completed in #{'%0.2f' % (Time.now - start)} seconds"
53
+ return value
54
+ end
55
+
56
+ #
57
+ # Parses command-line options
58
+ #
59
+ def parse_options(argv)
60
+ filters = []
61
+
62
+ opts = OptionParser.new do |opts|
63
+ opts.on("-I", "--include PATHS", "Prepend paths to $LOAD_PATH (colon separated list)", &Command.method(:opt_include_path))
64
+ opts.on("-o", "--report-root PATH", "Report output directory", &Command.method(:opt_report_root))
65
+ opts.on("-c", "--cache-root PATH", "Local cache directory", &Command.method(:opt_cache_root))
66
+
67
+ opts.on("-n", "--name PATTERN", "Trace stored procedures matching PATTERN") do |opt|
68
+ if m = opt.match(%r{^/(.+)/$})
69
+ filters << lambda{|p| p.name.match(m.captures.first) }
70
+ else
71
+ filters << lambda{|p| p.name === opt }
72
+ end
73
+ end
74
+
75
+ opts.on("-a", "--aggregate", "Aggregate data from the previous run", &Command.method(:opt_aggregate))
76
+ opts.on("-V", "--version", "Show version", &Command.method(:opt_version))
77
+ opts.on("-h", "--help", "Show this message") do
78
+ puts opts
79
+ exit! 0
80
+ end
81
+ end
82
+
83
+ begin
84
+ opts.parse! argv
85
+ raise OptionParser::MissingArgument, "no tests specified" if argv.empty?
86
+ rescue OptionParser::InvalidOption,
87
+ OptionParser::InvalidArgument,
88
+ OptionParser::MissingArgument
89
+ puts opts
90
+ puts
91
+ puts $!
92
+
93
+ exit! 1
94
+ end
95
+
96
+ test_paths = argv.map{|p| Dir[p] }.flatten.sort
97
+
98
+ return test_paths, filters
99
+ end
100
+
101
+ def load_tests(tests)
102
+ puts "Loading #{tests.size} test files"
103
+ benchmark { tests.each{|file| load file }}
104
+ end
105
+
106
+ #
107
+ # Writes all stored procedures in the database to disk
108
+ #
109
+ def dump_procedures(filters)
110
+ Command::Trace.dump_procedures(filters)
111
+ end
112
+
113
+ #
114
+ # Compiles all the stored procedures on disk and installs them
115
+ #
116
+ def trace(procedures)
117
+ benchmark { Command::Trace.trace(procedures) }
118
+ end
119
+
120
+ def clear_coverage
121
+ Command::Report.clear_coverage
122
+ end
123
+
124
+ def execute_tests
125
+ if defined? ::Test::Unit::AutoRunner
126
+ ::Test::Unit::AutoRunner.run
127
+ elsif defined? ::RSpec::Core
128
+ ::Rspec::Core::Runner.run(ARGV, $stderr, $stdout)
129
+ elsif defined? ::Spec::Runner
130
+ ::Spec::Runner.run
131
+ elsif defined? ::MiniTest::Unit
132
+ ::MiniTest::Unit.new.run(ARGV)
133
+ else
134
+ raise "Neither Test::Unit, MiniTest, nor RSpec were detected"
135
+ end
136
+ end
137
+
138
+ def store_coverage(profile)
139
+ benchmark { Command::Report.store_coverage(profile) }
140
+ end
141
+
142
+ def untrace(procedures)
143
+ benchmark { Command::Untrace.untrace(procedures) }
144
+ end
145
+
146
+ def create_index(procedures, profile)
147
+ benchmark { Command::Report.create_index(procedures, profile) }
148
+ end
149
+
150
+ def create_reports(procedures, profile)
151
+ benchmark { Command::Report.create_reports(procedures, profile) }
152
+ end
153
+
154
+ end
155
+ end
156
+ end
157
+ end