ngmoco-request-log-analyzer 1.4.2

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. 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
+