ngmoco-request-log-analyzer 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +8 -0
  6. data/bin/request-log-analyzer +114 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +213 -0
  11. data/lib/cli/tools.rb +46 -0
  12. data/lib/request_log_analyzer.rb +44 -0
  13. data/lib/request_log_analyzer/aggregator.rb +49 -0
  14. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  15. data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
  16. data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
  17. data/lib/request_log_analyzer/controller.rb +332 -0
  18. data/lib/request_log_analyzer/database.rb +102 -0
  19. data/lib/request_log_analyzer/database/base.rb +115 -0
  20. data/lib/request_log_analyzer/database/connection.rb +38 -0
  21. data/lib/request_log_analyzer/database/request.rb +22 -0
  22. data/lib/request_log_analyzer/database/source.rb +13 -0
  23. data/lib/request_log_analyzer/database/warning.rb +14 -0
  24. data/lib/request_log_analyzer/file_format.rb +160 -0
  25. data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
  26. data/lib/request_log_analyzer/file_format/apache.rb +141 -0
  27. data/lib/request_log_analyzer/file_format/merb.rb +67 -0
  28. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  29. data/lib/request_log_analyzer/file_format/rails.rb +176 -0
  30. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  31. data/lib/request_log_analyzer/filter.rb +30 -0
  32. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  33. data/lib/request_log_analyzer/filter/field.rb +42 -0
  34. data/lib/request_log_analyzer/filter/timespan.rb +45 -0
  35. data/lib/request_log_analyzer/line_definition.rb +111 -0
  36. data/lib/request_log_analyzer/log_processor.rb +99 -0
  37. data/lib/request_log_analyzer/mailer.rb +62 -0
  38. data/lib/request_log_analyzer/output.rb +113 -0
  39. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  40. data/lib/request_log_analyzer/output/html.rb +184 -0
  41. data/lib/request_log_analyzer/request.rb +175 -0
  42. data/lib/request_log_analyzer/source.rb +72 -0
  43. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  44. data/lib/request_log_analyzer/source/log_parser.rb +274 -0
  45. data/lib/request_log_analyzer/tracker.rb +206 -0
  46. data/lib/request_log_analyzer/tracker/duration.rb +104 -0
  47. data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
  48. data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
  49. data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
  50. data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
  51. data/request-log-analyzer.gemspec +40 -0
  52. data/spec/database.yml +23 -0
  53. data/spec/fixtures/apache_combined.log +5 -0
  54. data/spec/fixtures/apache_common.log +10 -0
  55. data/spec/fixtures/decompression.log +12 -0
  56. data/spec/fixtures/decompression.log.bz2 +0 -0
  57. data/spec/fixtures/decompression.log.gz +0 -0
  58. data/spec/fixtures/decompression.log.zip +0 -0
  59. data/spec/fixtures/decompression.tar.gz +0 -0
  60. data/spec/fixtures/decompression.tgz +0 -0
  61. data/spec/fixtures/header_and_footer.log +6 -0
  62. data/spec/fixtures/merb.log +84 -0
  63. data/spec/fixtures/merb_prefixed.log +9 -0
  64. data/spec/fixtures/multiple_files_1.log +5 -0
  65. data/spec/fixtures/multiple_files_2.log +2 -0
  66. data/spec/fixtures/rails.db +0 -0
  67. data/spec/fixtures/rails_1x.log +59 -0
  68. data/spec/fixtures/rails_22.log +12 -0
  69. data/spec/fixtures/rails_22_cached.log +10 -0
  70. data/spec/fixtures/rails_unordered.log +24 -0
  71. data/spec/fixtures/syslog_1x.log +5 -0
  72. data/spec/fixtures/test_file_format.log +13 -0
  73. data/spec/fixtures/test_language_combined.log +14 -0
  74. data/spec/fixtures/test_order.log +16 -0
  75. data/spec/integration/command_line_usage_spec.rb +84 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +151 -0
  78. data/spec/lib/helpers.rb +52 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +76 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +24 -0
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  94. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  95. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  96. data/spec/unit/file_format/rails_format_spec.rb +164 -0
  97. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  98. data/spec/unit/filter/field_filter_spec.rb +66 -0
  99. data/spec/unit/filter/filter_spec.rb +17 -0
  100. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  101. data/spec/unit/mailer_spec.rb +30 -0
  102. data/spec/unit/request_spec.rb +111 -0
  103. data/spec/unit/source/log_parser_spec.rb +119 -0
  104. data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
  105. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  106. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  107. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  108. data/spec/unit/tracker/tracker_api_spec.rb +124 -0
  109. data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
  110. data/tasks/github-gem.rake +323 -0
  111. data/tasks/request_log_analyzer.rake +26 -0
  112. metadata +220 -0
@@ -0,0 +1,18 @@
1
+ module RequestLogAnalyzer::Spec::Macros
2
+
3
+ def test_databases
4
+ require 'yaml'
5
+ hash = YAML.load(File.read("#{File.dirname(__FILE__)}/../database.yml"))
6
+ hash.inject({}) { |res, (name, h)| res[name] = h.map { |(k,v)| "#{k}=#{v}" }.join(';'); res }
7
+ end
8
+
9
+ # Create or return a new TestingFormat
10
+ def testing_format
11
+ @testing_format ||= TestingFormat.create
12
+ end
13
+
14
+ def default_orm_class_names
15
+ ['Warning', 'Request', 'Source']
16
+ end
17
+
18
+ end
@@ -0,0 +1,77 @@
1
+ module RequestLogAnalyzer::Spec::Matchers
2
+
3
+ class HasLineDefinition
4
+
5
+ def initialize(line_type)
6
+ @line_type = line_type.to_sym
7
+ @captures = []
8
+ end
9
+
10
+ def capturing(*captures)
11
+ @captures += captures
12
+ return self
13
+ end
14
+
15
+ def matches?(file_format)
16
+ file_format = file_format.create if file_format.kind_of?(Class)
17
+ if ld = file_format.line_definitions[@line_type]
18
+ @captures.all? { |c| ld.captures.map { |cd| cd[:name] }.include?(c) }
19
+ else
20
+ false
21
+ end
22
+ end
23
+ end
24
+
25
+ class ParseLine
26
+
27
+ def initialize(line)
28
+ @line = line
29
+ @captures = {}
30
+ @line_type = nil
31
+ end
32
+
33
+ def failure_message
34
+ @failure_message
35
+ end
36
+
37
+ def as(line_type)
38
+ @line_type = line_type
39
+ return self
40
+ end
41
+
42
+ def and_capture(captures)
43
+ @captures = captures
44
+ return self
45
+ end
46
+
47
+ def fail(message)
48
+ @failure_message = message
49
+ return false
50
+ end
51
+
52
+ def matches?(file_format)
53
+ if @line_hash = file_format.parse_line(@line)
54
+ if @line_type.nil? || @line_hash[:line_definition].name == @line_type
55
+ @request = file_format.request(@line_hash)
56
+ @captures.each do |key, value|
57
+ return fail("Expected line #{@line.inspect}\n to capture #{key.inspect} as #{value.inspect} but was #{@request[key].inspect}.") if @request[key] != value
58
+ end
59
+ return true
60
+ else
61
+ return fail("The line should match the #{@line_type.inspect} line definition, but matched #{@line_hash[:line_definition].name.inspect} instead.")
62
+ end
63
+ else
64
+ return fail("The line did not match any line definition.")
65
+ end
66
+ end
67
+ end
68
+
69
+ def have_line_definition(line_type)
70
+ return HasLineDefinition.new(line_type)
71
+ end
72
+
73
+ def parse_line(line)
74
+ ParseLine.new(line)
75
+ end
76
+
77
+ end
@@ -0,0 +1,76 @@
1
+ module RequestLogAnalyzer::Spec::Mocks
2
+
3
+ def mock_source
4
+ source = mock('RequestLogAnalyzer::Source::Base')
5
+ source.stub!(:file_format).and_return(testing_format)
6
+ source.stub!(:parsed_requests).and_return(2)
7
+ source.stub!(:skipped_requests).and_return(1)
8
+ source.stub!(:parse_lines).and_return(10)
9
+
10
+ source.stub!(:warning=)
11
+ source.stub!(:progress=)
12
+ source.stub!(:source_changes=)
13
+
14
+ source.stub!(:prepare)
15
+ source.stub!(:finalize)
16
+
17
+ source.stub!(:each_request).and_return do |block|
18
+ block.call(testing_format.request(:field => 'value1'))
19
+ block.call(testing_format.request(:field => 'value2'))
20
+ end
21
+
22
+ return source
23
+ end
24
+
25
+ def mock_io
26
+ mio = mock('IO')
27
+ mio.stub!(:print)
28
+ mio.stub!(:puts)
29
+ mio.stub!(:write)
30
+ return mio
31
+ end
32
+
33
+ def mock_output
34
+ output = mock('RequestLogAnalyzer::Output::Base')
35
+ output.stub!(:header)
36
+ output.stub!(:footer)
37
+ output.stub!(:puts)
38
+ output.stub!(:<<)
39
+ output.stub!(:colorize).and_return("Fancy text")
40
+ output.stub!(:link)
41
+ output.stub!(:title)
42
+ output.stub!(:line)
43
+ output.stub!(:with_style)
44
+ output.stub!(:table).and_yield([])
45
+ output.stub!(:io).and_return(mock_io)
46
+ output.stub!(:options).and_return({})
47
+ output.stub!(:slice_results).and_return { |a| a }
48
+ return output
49
+ end
50
+
51
+ def mock_database(*stubs)
52
+ database = mock('RequestLogAnalyzer::Database')
53
+ database.stub!(:connect)
54
+ database.stub!(:disconnect)
55
+ database.stub!(:connection).and_return(mock_connection)
56
+ stubs.each { |s| database.stub!(s)}
57
+ return database
58
+ end
59
+
60
+ def mock_connection
61
+ table_creator = mock('ActiveRecord table creator')
62
+ table_creator.stub!(:column)
63
+
64
+ connection = mock('ActiveRecord::Base.connection')
65
+ connection.stub!(:add_index)
66
+ connection.stub!(:remove_index)
67
+ connection.stub!(:table_exists?).and_return(false)
68
+ connection.stub!(:create_table).and_yield(table_creator).and_return(true)
69
+ connection.stub!(:table_creator).and_return(table_creator)
70
+ return connection
71
+ end
72
+
73
+ def request_counter
74
+ @request_counter ||= mock('aggregator to count request')
75
+ end
76
+ end
@@ -0,0 +1,46 @@
1
+ # Simple log file specification, used to test log parser.
2
+ class TestingFormat < RequestLogAnalyzer::FileFormat::Base
3
+
4
+ format_definition.first do |line|
5
+ line.header = true
6
+ line.teaser = /processing /
7
+ line.regexp = /processing request (\d+)/
8
+ line.captures = [{ :name => :request_no, :type => :integer }]
9
+ end
10
+
11
+ format_definition.test do |line|
12
+ line.teaser = /testing /
13
+ line.regexp = /testing is (\w+)(?: in (\d+\.\d+)ms)?/
14
+ line.captures = [{ :name => :test_capture, :type => :test_type },
15
+ { :name => :duration, :type => :duration, :unit => :msec }]
16
+ end
17
+
18
+ format_definition.eval do |line|
19
+ line.regexp = /evaluation (\{.*\})/
20
+ line.captures = [{ :name => :evaluated, :type => :eval, :provides => { :greating => :string, :what => :string } }]
21
+ end
22
+
23
+ format_definition.last do |line|
24
+ line.footer = true
25
+ line.teaser = /finishing /
26
+ line.regexp = /finishing request (\d+)/
27
+ line.captures = [{ :name => :request_no, :type => :integer }]
28
+ end
29
+
30
+ format_definition.combined do |line|
31
+ line.header = true
32
+ line.footer = true
33
+ line.regexp = /this is a header and footer line/
34
+ end
35
+
36
+ report do |analyze|
37
+ analyze.frequency :test_capture, :title => 'What is testing exactly?'
38
+ end
39
+
40
+ class Request < RequestLogAnalyzer::Request
41
+ def convert_test_type(value, definition)
42
+ "Testing is #{value}"
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,24 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'rubygems'
5
+ require 'spec/autorun'
6
+ require 'request_log_analyzer'
7
+
8
+ module RequestLogAnalyzer::Spec
9
+ end
10
+
11
+ # Include all files in the spec_helper directory
12
+ Dir[File.dirname(__FILE__) + "/lib/**/*.rb"].each do |file|
13
+ require file
14
+ end
15
+
16
+ Dir.mkdir("#{File.dirname(__FILE__)}/../tmp") unless File.exist?("#{File.dirname(__FILE__)}/../tmp")
17
+
18
+ Spec::Runner.configure do |config|
19
+ config.include RequestLogAnalyzer::Spec::Matchers
20
+ config.include RequestLogAnalyzer::Spec::Mocks
21
+ config.include RequestLogAnalyzer::Spec::Helpers
22
+
23
+ config.extend RequestLogAnalyzer::Spec::Macros
24
+ end
@@ -0,0 +1,93 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Aggregator::DatabaseInserter do
4
+
5
+ before(:all) do
6
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(testing_format)
7
+ end
8
+
9
+ # The prepare method is called before the parsing starts. It should establish a connection
10
+ # to a database that is suitable for inserting requests later on.
11
+ describe '#prepare' do
12
+
13
+ before(:each) do
14
+ @database = mock_database(:create_database_schema!, :drop_database_schema!, :file_format=)
15
+ @database_inserter = RequestLogAnalyzer::Aggregator::DatabaseInserter.new(@log_parser)
16
+ RequestLogAnalyzer::Database.stub!(:new).and_return(@database)
17
+ end
18
+
19
+ it 'should establish the database connection' do
20
+ RequestLogAnalyzer::Database.should_receive(:new).and_return(@database)
21
+ @database_inserter.prepare
22
+ end
23
+
24
+ it "should set the file_format" do
25
+ @database.should_receive(:file_format=).with(testing_format)
26
+ @database_inserter.prepare
27
+ end
28
+
29
+ it 'should create the database schema during preparation' do
30
+ @database.should_receive(:create_database_schema!)
31
+ @database_inserter.prepare
32
+ end
33
+
34
+ it 'should not drop the database schema during preparation if not requested' do
35
+ @database.should_not_receive(:drop_database_schema!)
36
+ @database_inserter.prepare
37
+ end
38
+
39
+ it 'should drop the database schema during preparation if requested' do
40
+ @database_inserter.options[:reset_database] = true
41
+ @database.should_receive(:drop_database_schema!)
42
+ @database_inserter.prepare
43
+ end
44
+ end
45
+
46
+ test_databases.each do |name, connection|
47
+
48
+ context "using a #{name} database" do
49
+
50
+ before(:each) do
51
+ @database_inserter = RequestLogAnalyzer::Aggregator::DatabaseInserter.new(@log_parser, :database => connection, :reset_database => true)
52
+ @database_inserter.prepare
53
+
54
+ @incomplete_request = testing_format.request( {:line_type => :first, :request_no => 564})
55
+ @completed_request = testing_format.request( {:line_type => :first, :request_no => 564},
56
+ {:line_type => :test, :test_capture => "awesome"},
57
+ {:line_type => :test, :test_capture => "indeed"},
58
+ {:line_type => :eval, :evaluated => { :greating => 'howdy'}, :greating => 'howdy' },
59
+ {:line_type => :last, :request_no => 564})
60
+ end
61
+
62
+ after(:each) do
63
+ @database_inserter.database.send :remove_orm_classes!
64
+ end
65
+
66
+ it "should insert a record in the request table" do
67
+ lambda {
68
+ @database_inserter.aggregate(@incomplete_request)
69
+ }.should change(RequestLogAnalyzer::Database::Request, :count).from(0).to(1)
70
+ end
71
+
72
+ it "should insert a record in the first_lines table" do
73
+ lambda {
74
+ @database_inserter.aggregate(@incomplete_request)
75
+ }.should change(@database_inserter.database.get_class(:first), :count).from(0).to(1)
76
+ end
77
+
78
+ it "should insert records in all relevant line tables" do
79
+ @database_inserter.aggregate(@completed_request)
80
+ request = RequestLogAnalyzer::Database::Request.first
81
+ request.should have(2).test_lines
82
+ request.should have(1).first_lines
83
+ request.should have(1).eval_lines
84
+ request.should have(1).last_lines
85
+ end
86
+
87
+ it "should log a warning in the warnings table" do
88
+ RequestLogAnalyzer::Database::Warning.should_receive(:create!).with(hash_including(:warning_type => 'test_warning'))
89
+ @database_inserter.warning(:test_warning, "Testing the warning system", 12)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Aggregator::Summarizer do
4
+
5
+ before(:each) do
6
+ @summarizer = RequestLogAnalyzer::Aggregator::Summarizer.new(mock_source, :output => mock_output)
7
+ @summarizer.prepare
8
+ end
9
+
10
+ it "not raise exception when creating a report after aggregating multiple requests" do
11
+ @summarizer.aggregate(request(:data => 'bluh1'))
12
+ @summarizer.aggregate(request(:data => 'bluh2'))
13
+
14
+ lambda { @summarizer.report(mock_output) }.should_not raise_error
15
+ end
16
+
17
+ it "not raise exception when creating a report after aggregating a single request" do
18
+ @summarizer.aggregate(request(:data => 'bluh1'))
19
+ lambda { @summarizer.report(mock_output) }.should_not raise_error
20
+ end
21
+
22
+ it "not raise exception when creating a report after aggregating no requests" do
23
+ lambda { @summarizer.report(mock_output) }.should_not raise_error
24
+ end
25
+
26
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Controller do
4
+
5
+ it "should use a custom output generator correctly" do
6
+
7
+ mock_output = mock('RequestLogAnalyzer::Output::Base')
8
+ mock_output.stub!(:io).and_return(mock_io)
9
+ mock_output.should_receive(:header)
10
+ mock_output.should_receive(:footer)
11
+
12
+ controller = RequestLogAnalyzer::Controller.new(mock_source, :output => mock_output)
13
+
14
+ controller.run!
15
+ end
16
+
17
+ it "should call aggregators correctly when run" do
18
+ controller = RequestLogAnalyzer::Controller.new(mock_source, :output => mock_output)
19
+
20
+ mock_aggregator = mock('RequestLogAnalyzer::Aggregator::Base')
21
+ mock_aggregator.should_receive(:prepare).once.ordered
22
+ mock_aggregator.should_receive(:aggregate).with(an_instance_of(testing_format.request_class)).twice.ordered
23
+ mock_aggregator.should_receive(:finalize).once.ordered
24
+ mock_aggregator.should_receive(:report).once.ordered
25
+
26
+ controller.aggregators << mock_aggregator
27
+ controller.run!
28
+ end
29
+
30
+ it "should call filters when run" do
31
+ controller = RequestLogAnalyzer::Controller.new(mock_source, :output => mock_output)
32
+
33
+ mock_filter = mock('RequestLogAnalyzer::Filter::Base')
34
+ mock_filter.should_receive(:filter).twice.and_return(nil)
35
+ controller.should_receive(:aggregate_request).twice.and_return(nil)
36
+
37
+ controller.filters << mock_filter
38
+ controller.run!
39
+ end
40
+
41
+ end
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ require 'request_log_analyzer/log_processor'
4
+
5
+ describe RequestLogAnalyzer::LogProcessor, 'stripping log files' do
6
+
7
+ before(:each) do
8
+ @log_stripper = RequestLogAnalyzer::LogProcessor.new(testing_format, :strip, {})
9
+ end
10
+
11
+ it "should remove a junk line" do
12
+ @log_stripper.strip_line("junk line\n").should be_empty
13
+ end
14
+
15
+ it "should keep a teaser line intact" do
16
+ @log_stripper.strip_line("processing 1234\n").should be_empty
17
+ end
18
+ end
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Database::Base do
4
+
5
+ describe '.subclass_from_line_definition' do
6
+ before(:all) do
7
+ @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, { :regexp => /Testing (\w+), tries\: (\d+)/,
8
+ :captures => [{ :name => :what, :type => :string }, { :name => :tries, :type => :integer },
9
+ { :name => :evaluated, :type => :hash, :provides => {:evaluated_field => :duration} }]})
10
+ end
11
+
12
+ before(:each) do
13
+ @orm_class = mock('Line ActiveRecord::Base class')
14
+ @orm_class.stub!(:set_table_name)
15
+ @orm_class.stub!(:belongs_to)
16
+ @orm_class.stub!(:serialize)
17
+ @orm_class.stub!(:line_definition=)
18
+
19
+ Class.stub!(:new).with(RequestLogAnalyzer::Database::Base).and_return(@orm_class)
20
+
21
+ RequestLogAnalyzer::Database::Request.stub!(:has_many)
22
+ RequestLogAnalyzer::Database::Source.stub!(:has_many)
23
+
24
+ @database = mock_database
25
+ RequestLogAnalyzer::Database::Base.stub!(:database).and_return(@database)
26
+ end
27
+
28
+ it "should create a new subclass using the Base class as parent" do
29
+ Class.should_receive(:new).with(RequestLogAnalyzer::Database::Base).and_return(@orm_class)
30
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
31
+ end
32
+
33
+ it "should store the LineDefinition" do
34
+ @orm_class.should_receive(:line_definition=).with(@line_definition)
35
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
36
+ end
37
+
38
+ it "should set the table name for the subclass" do
39
+ @orm_class.should_receive(:set_table_name).with('test_lines')
40
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
41
+ end
42
+
43
+ it "should set the :belongs_to relationship with the Request class" do
44
+ @orm_class.should_receive(:belongs_to).with(:request)
45
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
46
+ end
47
+
48
+ it "should set a :has_many relationship in the request class" do
49
+ RequestLogAnalyzer::Database::Request.should_receive(:has_many).with(:test_lines)
50
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
51
+ end
52
+
53
+ it "should set a :has_many relationship in the source class" do
54
+ RequestLogAnalyzer::Database::Source.should_receive(:has_many).with(:test_lines)
55
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
56
+ end
57
+
58
+ it "should set the :belongs_to relationship with the Source class" do
59
+ @orm_class.should_receive(:belongs_to).with(:source)
60
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
61
+ end
62
+
63
+ it "should serialize a complex field" do
64
+ @orm_class.should_receive(:serialize).with(:evaluated, Hash)
65
+ RequestLogAnalyzer::Database::Base.subclass_from_line_definition(@line_definition)
66
+ end
67
+
68
+ end
69
+
70
+ describe '.subclass_from_table' do
71
+ before(:each) do
72
+
73
+ RequestLogAnalyzer::Database::Request.stub!(:has_many)
74
+ RequestLogAnalyzer::Database::Source.stub!(:has_many)
75
+
76
+ @database = mock_database
77
+ @database.connection.stub!(:table_exists?).and_return(true)
78
+ RequestLogAnalyzer::Database::Base.stub!(:database).and_return(@database)
79
+
80
+ @klass = mock('ActiveRecord ORM class')
81
+ @klass.stub!(:column_names).and_return(['id', 'request_id', 'source_id', 'lineno', 'duration'])
82
+ @klass.stub!(:set_table_name)
83
+ @klass.stub!(:belongs_to)
84
+ Class.stub!(:new).with(RequestLogAnalyzer::Database::Base).and_return(@klass)
85
+ end
86
+
87
+ it "should create a new subclass using the Base class as parent" do
88
+ Class.should_receive(:new).with(RequestLogAnalyzer::Database::Base).and_return(@klass)
89
+ RequestLogAnalyzer::Database::Base.subclass_from_table('completed_lines')
90
+ end
91
+
92
+ it "should set the table name" do
93
+ @klass.should_receive(:set_table_name).with('completed_lines')
94
+ RequestLogAnalyzer::Database::Base.subclass_from_table('completed_lines')
95
+ end
96
+
97
+ it "should create the :belongs_to relation to the request class" do
98
+ @klass.should_receive(:belongs_to).with(:request)
99
+ RequestLogAnalyzer::Database::Base.subclass_from_table('completed_lines')
100
+ end
101
+
102
+ it "should create the :has_many relation in the request class" do
103
+ RequestLogAnalyzer::Database::Request.should_receive(:has_many).with(:completed_lines)
104
+ RequestLogAnalyzer::Database::Base.subclass_from_table('completed_lines')
105
+ end
106
+
107
+ it "should create the :belongs_to relation to the source class" do
108
+ @klass.should_receive(:belongs_to).with(:source)
109
+ RequestLogAnalyzer::Database::Base.subclass_from_table('completed_lines')
110
+ end
111
+
112
+ it "should create the :has_many relation in the request class" do
113
+ RequestLogAnalyzer::Database::Source.should_receive(:has_many).with(:completed_lines)
114
+ RequestLogAnalyzer::Database::Base.subclass_from_table('completed_lines')
115
+ end
116
+
117
+ end
118
+
119
+ describe '#create_table' do
120
+
121
+ before(:all) do
122
+ @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, { :regexp => /Testing (\w+), tries\: (\d+)/,
123
+ :captures => [{ :name => :what, :type => :string }, { :name => :tries, :type => :integer },
124
+ { :name => :evaluated, :type => :hash, :provides => {:evaluated_field => :duration} }]})
125
+ end
126
+
127
+ before(:each) do
128
+ @database = RequestLogAnalyzer::Database.new
129
+ @database.stub!(:connection).and_return(mock_connection)
130
+ @klass = @database.load_activerecord_class(@line_definition)
131
+ @klass.stub!(:table_exists?).and_return(false)
132
+ end
133
+
134
+ after(:each) do
135
+ @klass.drop_table!
136
+ @database.remove_orm_classes!
137
+ end
138
+
139
+ it "should call create_table with the correct table name" do
140
+ @database.connection.should_receive(:create_table).with(:test_lines)
141
+ @klass.create_table!
142
+ end
143
+
144
+ it "should not create a table based on the line type name if it already exists" do
145
+ @klass.stub!(:table_exists?).and_return(true)
146
+ @database.connection.should_not_receive(:create_table).with(:test_lines)
147
+ @klass.create_table!
148
+ end
149
+
150
+ it "should create an index on the request_id field" do
151
+ @database.connection.should_receive(:add_index).with(:test_lines, [:request_id])
152
+ @klass.create_table!
153
+ end
154
+
155
+ it "should create an index on the source_id field" do
156
+ @database.connection.should_receive(:add_index).with(:test_lines, [:source_id])
157
+ @klass.create_table!
158
+ end
159
+
160
+ it "should create a request_id field to link the requests together" do
161
+ @database.connection.table_creator.should_receive(:column).with(:request_id, :integer)
162
+ @klass.create_table!
163
+ end
164
+
165
+ it "should create a lineno field to save the location of the line in the original file" do
166
+ @database.connection.table_creator.should_receive(:column).with(:lineno, :integer)
167
+ @klass.create_table!
168
+ end
169
+
170
+ it "should create a field of the correct type for every defined capture field" do
171
+ @database.connection.table_creator.should_receive(:column).with(:what, :string)
172
+ @database.connection.table_creator.should_receive(:column).with(:tries, :integer)
173
+ @database.connection.table_creator.should_receive(:column).with(:evaluated, :text)
174
+ @klass.create_table!
175
+ end
176
+
177
+ it "should create a field of the correct type for every provided field" do
178
+ @database.connection.table_creator.should_receive(:column).with(:evaluated_field, :double)
179
+ @klass.create_table!
180
+ end
181
+ end
182
+ end
183
+