request-log-analyzer 1.2.1 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/Rakefile +1 -1
  2. data/lib/request_log_analyzer/aggregator/database.rb +59 -35
  3. data/lib/request_log_analyzer/controller.rb +12 -7
  4. data/lib/request_log_analyzer/line_definition.rb +9 -0
  5. data/lib/request_log_analyzer/source/log_parser.rb +4 -0
  6. data/spec/fixtures/header_and_footer.log +6 -0
  7. data/spec/integration/command_line_usage_spec.rb +0 -2
  8. data/spec/lib/{helper.rb → helpers.rb} +1 -3
  9. data/spec/lib/macros.rb +2 -0
  10. data/spec/lib/matchers.rb +63 -0
  11. data/spec/lib/testing_format.rb +6 -0
  12. data/spec/spec.opts +3 -0
  13. data/spec/spec_helper.rb +13 -4
  14. data/spec/unit/aggregator/database_spec.rb +208 -0
  15. data/spec/unit/aggregator/summarizer_spec.rb +0 -2
  16. data/spec/unit/controller/controller_spec.rb +0 -2
  17. data/spec/unit/controller/log_processor_spec.rb +0 -2
  18. data/spec/unit/file_format/file_format_api_spec.rb +50 -71
  19. data/spec/unit/file_format/line_definition_spec.rb +49 -45
  20. data/spec/unit/file_format/merb_format_spec.rb +0 -1
  21. data/spec/unit/file_format/rails_format_spec.rb +0 -2
  22. data/spec/unit/filter/anonymize_filter_spec.rb +0 -1
  23. data/spec/unit/filter/field_filter_spec.rb +0 -3
  24. data/spec/unit/filter/filter_spec.rb +17 -0
  25. data/spec/unit/filter/timespan_filter_spec.rb +0 -3
  26. data/spec/unit/source/log_parser_spec.rb +5 -4
  27. data/spec/unit/source/request_spec.rb +17 -4
  28. data/spec/unit/tracker/duration_tracker_spec.rb +0 -6
  29. data/spec/unit/tracker/frequency_tracker_spec.rb +0 -6
  30. data/spec/unit/tracker/hourly_spread_spec.rb +0 -4
  31. data/spec/unit/tracker/timespan_tracker_spec.rb +0 -4
  32. data/spec/unit/tracker/tracker_api_spec.rb +0 -2
  33. data/tasks/rspec.rake +8 -1
  34. metadata +12 -6
  35. data/spec/unit/aggregator/database_inserter_spec.rb +0 -106
data/Rakefile CHANGED
@@ -1,3 +1,3 @@
1
1
  Dir[File.dirname(__FILE__) + "/tasks/*.rake"].each { |file| load(file) }
2
2
 
3
- task :default => :spec
3
+ task :default => "spec:fancy"
@@ -16,10 +16,13 @@ module RequestLogAnalyzer::Aggregator
16
16
  # created to log all parse warnings.
17
17
  class Database < Base
18
18
 
19
+ attr_reader :request_class, :request_count, :orm_module, :warning_class
20
+
19
21
  # Establishes a connection to the database and creates the necessary database schema for the
20
22
  # current file format
21
23
  def prepare
22
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => options[:database])
24
+ initialize_orm_module!
25
+ establish_database_connection!
23
26
  File.unlink(options[:database]) if File.exist?(options[:database]) # TODO: keep old database?
24
27
  create_database_schema!
25
28
  end
@@ -28,9 +31,9 @@ module RequestLogAnalyzer::Aggregator
28
31
  # This will create a record in the requests table and create a record for every line that has been parsed,
29
32
  # in which the captured values will be stored.
30
33
  def aggregate(request)
31
- @request_object = @request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
34
+ @request_object = request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
32
35
  request.lines.each do |line|
33
- class_columns = @orm_module.const_get("#{line[:line_type]}_line".classify).column_names.reject { |column| ['id'].include?(column) }
36
+ class_columns = orm_module.const_get("#{line[:line_type]}_line".classify).column_names.reject { |column| ['id'].include?(column) }
34
37
  attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
35
38
  @request_object.send("#{line[:line_type]}_lines").build(attributes)
36
39
  end
@@ -41,13 +44,13 @@ module RequestLogAnalyzer::Aggregator
41
44
 
42
45
  # Finalizes the aggregator by closing the connection to the database
43
46
  def finalize
44
- @request_count = @orm_module::Request.count
45
- ActiveRecord::Base.remove_connection
47
+ @request_count = orm_module::Request.count
48
+ remove_database_connection!
46
49
  end
47
50
 
48
51
  # Records w warining in the warnings table.
49
52
  def warning(type, message, lineno)
50
- @orm_module::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
53
+ warning_class.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
51
54
  end
52
55
 
53
56
  # Prints a short report of what has been inserted into the database
@@ -63,46 +66,72 @@ module RequestLogAnalyzer::Aggregator
63
66
 
64
67
  protected
65
68
 
69
+ # Create a module and a default subclass of ActiveRecord::Base on which to establish the database connection
70
+ def initialize_orm_module!
71
+
72
+ # Create a Database module in the file format if it does not yet exists
73
+ file_format.class.const_set('Database', Module.new) unless file_format.class.const_defined?('Database')
74
+ @orm_module = file_format.class.const_get('Database')
75
+
76
+ # Register the base activerecord class
77
+ unless orm_module.const_defined?('Base')
78
+ orm_base_class = Class.new(ActiveRecord::Base)
79
+ orm_base_class.abstract_class = true
80
+ orm_module.const_set('Base', orm_base_class)
81
+ end
82
+ end
83
+
84
+ # Established a connection with the database for this session
85
+ def establish_database_connection!
86
+ orm_module::Base.establish_connection(:adapter => 'sqlite3', :database => options[:database])
87
+ ActiveRecord::Migration.class_eval("def self.connection; #{@orm_module.to_s}::Base.connection; end ")
88
+ end
89
+
90
+ def remove_database_connection!
91
+ ActiveRecord::Migration.class_eval("def self.connection; ActiveRecord::Base.connection; end ")
92
+ orm_module::Base.remove_connection
93
+ end
94
+
66
95
  # This function creates a database table for a given line definition.
67
96
  # It will create a field for every capture in the line, and adds a lineno field to indicate at
68
97
  # what line in the original file the line was found, and a request_id to link lines related
69
98
  # to the same request. It will also create an index in the request_id field to speed up queries.
70
- def create_database_table(name, definition)
71
- ActiveRecord::Migration.verbose = options[:debug]
72
- ActiveRecord::Migration.create_table("#{name}_lines") do |t|
99
+ def create_database_table(definition)
100
+ ActiveRecord::Migration.verbose = options[:debug] # keep it down a notch please
101
+ ActiveRecord::Migration.create_table("#{definition.name}_lines") do |t|
102
+
103
+ # Add default fields for every line type
73
104
  t.column(:request_id, :integer)
74
105
  t.column(:lineno, :integer)
106
+
75
107
  definition.captures.each do |capture|
76
-
77
108
  # Add a field for every capture
78
109
  t.column(capture[:name], column_type(capture[:type]))
79
-
80
- # If the capture provides other field as well, create them too
81
- if capture[:provides].kind_of?(Hash)
82
- capture[:provides].each do |field, field_type|
83
- t.column(field, column_type(field_type))
84
- end
85
- end
110
+
111
+ # If the capture provides other field as well, create columns for them, too
112
+ capture[:provides].each { |field, field_type| t.column(field, column_type(field_type)) } if capture[:provides].kind_of?(Hash)
86
113
  end
87
114
  end
88
- ActiveRecord::Migration.add_index("#{name}_lines", [:request_id])
115
+
116
+ # Create an index on the request_id column to support faster querying
117
+ ActiveRecord::Migration.add_index("#{definition.name}_lines", [:request_id])
89
118
  end
90
119
 
91
120
  # Creates an ActiveRecord class for a given line definition.
92
121
  # A subclass of ActiveRecord::Base is created and an association with the Request class is
93
122
  # created using belongs_to / has_many. This association will later be used to create records
94
123
  # in the corresponding table. This table should already be created before this method is called.
95
- def create_activerecord_class(name, definition)
96
- class_name = "#{name}_line".camelize
97
- klass = Class.new(ActiveRecord::Base)
124
+ def create_activerecord_class(definition)
125
+ class_name = "#{definition.name}_line".camelize
126
+ klass = Class.new(orm_module::Base)
98
127
  klass.send(:belongs_to, :request)
99
128
 
100
129
  definition.captures.each do |capture|
101
130
  klass.send(:serialize, capture[:name], Hash) if capture[:provides]
102
131
  end
103
132
 
104
- @orm_module.const_set(class_name, klass) unless @orm_module.const_defined?(class_name)
105
- @request_class.send(:has_many, "#{name}_lines".to_sym)
133
+ orm_module.const_set(class_name, klass) unless orm_module.const_defined?(class_name)
134
+ request_class.send(:has_many, "#{definition.name}_lines".to_sym)
106
135
  end
107
136
 
108
137
  # Creates a requests table, in which a record is created for every request. It also creates an
@@ -114,8 +143,8 @@ module RequestLogAnalyzer::Aggregator
114
143
  t.integer :last_lineno
115
144
  end
116
145
 
117
- @orm_module.const_set('Request', Class.new(ActiveRecord::Base)) unless @orm_module.const_defined?('Request')
118
- @request_class = @orm_module.const_get('Request')
146
+ orm_module.const_set('Request', Class.new(orm_module::Base)) unless orm_module.const_defined?('Request')
147
+ @request_class = orm_module.const_get('Request')
119
148
  end
120
149
 
121
150
  # Creates a warnings table and a corresponding Warning class to communicate with this table using ActiveRecord.
@@ -127,25 +156,19 @@ module RequestLogAnalyzer::Aggregator
127
156
  t.integer :lineno
128
157
  end
129
158
 
130
- @orm_module.const_set('Warning', Class.new(ActiveRecord::Base)) unless @orm_module.const_defined?('Warning')
159
+ orm_module.const_set('Warning', Class.new(orm_module::Base)) unless orm_module.const_defined?('Warning')
160
+ @warning_class = orm_module.const_get('Warning')
131
161
  end
132
162
 
133
163
  # Creates the database schema and related ActiveRecord::Base subclasses that correspond to the
134
164
  # file format definition. These ORM classes will later be used to create records in the database.
135
165
  def create_database_schema!
136
-
137
- if file_format.class.const_defined?('Database')
138
- @orm_module = file_format.class.const_get('Database')
139
- else
140
- @orm_module = file_format.class.const_set('Database', Module.new)
141
- end
142
-
143
166
  create_request_table_and_class
144
167
  create_warning_table_and_class
145
168
 
146
169
  file_format.line_definitions.each do |name, definition|
147
- create_database_table(name, definition)
148
- create_activerecord_class(name, definition)
170
+ create_database_table(definition)
171
+ create_activerecord_class(definition)
149
172
  end
150
173
  end
151
174
 
@@ -154,6 +177,7 @@ module RequestLogAnalyzer::Aggregator
154
177
  def column_type(type_indicator)
155
178
  case type_indicator
156
179
  when :eval; :text
180
+ when :hash; :text
157
181
  when :text; :text
158
182
  when :string; :string
159
183
  when :sec; :double
@@ -191,14 +191,11 @@ module RequestLogAnalyzer
191
191
  def run!
192
192
 
193
193
  @aggregators.each { |agg| agg.prepare }
194
+ install_signal_handlers
194
195
 
195
- begin
196
- @source.each_request do |request|
197
- aggregate_request(filter_request(request))
198
- end
199
- rescue Interrupt => e
200
- handle_progress(:interrupted)
201
- puts "Caught interrupt! Stopped parsing."
196
+ @source.each_request do |request|
197
+ aggregate_request(filter_request(request))
198
+ break if @interrupted
202
199
  end
203
200
 
204
201
  @aggregators.each { |agg| agg.finalize }
@@ -219,5 +216,13 @@ module RequestLogAnalyzer
219
216
  end
220
217
  end
221
218
 
219
+ def install_signal_handlers
220
+ Signal.trap("INT") do
221
+ handle_progress(:interrupted)
222
+ puts "Caught interrupt! Stopping parsing..."
223
+ @interrupted = true
224
+ end
225
+ end
226
+
222
227
  end
223
228
  end
@@ -66,6 +66,8 @@ module RequestLogAnalyzer
66
66
 
67
67
  alias :=~ :matches
68
68
 
69
+ # matches the line and converts the captured values using the request's
70
+ # convert_value function.
69
71
  def match_for(line, request, lineno = nil, parser = nil)
70
72
  if match_info = matches(line, lineno, parser)
71
73
  convert_captured_values(match_info[:captures], request)
@@ -74,11 +76,18 @@ module RequestLogAnalyzer
74
76
  end
75
77
  end
76
78
 
79
+ # Updates a captures hash using the converters specified in the request
80
+ # and handle the :provides option in the line definition.
77
81
  def convert_captured_values(values, request)
78
82
  value_hash = {}
79
83
  captures.each_with_index do |capture, index|
84
+
85
+ # convert the value using the request convert_value function
80
86
  converted = request.convert_value(values[index], capture)
81
87
  value_hash[capture[:name]] ||= converted
88
+
89
+ # Add items directly to the resulting hash from the converted value
90
+ # if it is a hash and they are set in the :provides hash for this line definition
82
91
  if converted.kind_of?(Hash) && capture[:provides].kind_of?(Hash)
83
92
  capture[:provides].each do |name, type|
84
93
  value_hash[name] ||= request.convert_value(converted[name], { :type => type })
@@ -207,6 +207,10 @@ module RequestLogAnalyzer::Source
207
207
  warn(:unclosed_request, "Encountered header line (#{request_data[:line_definition].name.inspect}), but previous request was not closed!")
208
208
  @current_request = nil # remove all data that was parsed, skip next request as well.
209
209
  end
210
+ if footer_line?(request_data)
211
+ handle_request(@current_request, &block) # yield @current_request
212
+ @current_request = nil
213
+ end
210
214
  else
211
215
  @current_request = @file_format.request(request_data)
212
216
  end
@@ -0,0 +1,6 @@
1
+ start!
2
+ this is a header and footer line
3
+ blah
4
+
5
+ this is a header and footer line
6
+ blah
@@ -2,8 +2,6 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
3
  describe RequestLogAnalyzer, 'running from command line' do
4
4
 
5
- include RequestLogAnalyzer::Spec::Helper
6
-
7
5
  before(:each) do
8
6
  cleanup_temp_files!
9
7
  end
@@ -1,7 +1,5 @@
1
- module RequestLogAnalyzer::Spec::Helper
1
+ module RequestLogAnalyzer::Spec::Helpers
2
2
 
3
- include RequestLogAnalyzer::Spec::Mocks
4
-
5
3
  # Create or return a new TestingFormat
6
4
  def testing_format
7
5
  @testing_format ||= TestingFormat.new
@@ -0,0 +1,2 @@
1
+ module RequestLogAnalyzer::Spec::Macros
2
+ end
@@ -0,0 +1,63 @@
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
+ if file_format.new.line_definitions.include?(@line_type)
17
+ ld = file_format.new.line_definitions[@line_type]
18
+ @captures.all? { |c| ld.captures.include?(c) }
19
+ else
20
+ false
21
+ end
22
+ end
23
+ end
24
+
25
+ class Parse
26
+ def initialize(line)
27
+ @line = line
28
+ @captures = nil
29
+ end
30
+
31
+ def failure_message
32
+ message = "expected to parse the provided line"
33
+ if @found_captures
34
+ message << "with captures #{@captures.inspect}, but captured #{@found_captures.inspect}"
35
+ end
36
+ return message
37
+ end
38
+
39
+ def capturing(*captures)
40
+ @captures = captures
41
+ return self
42
+ end
43
+
44
+ def matches?(line_definition)
45
+ hash = line_definition.matches(@line)
46
+ if hash
47
+ @found_captures = hash[:captures].to_a
48
+ @captures.nil? ? true : @found_captures.eql?(@captures)
49
+ else
50
+ return false
51
+ end
52
+ end
53
+ end
54
+
55
+ def have_line_definition(line_type)
56
+ return HasLineDefinition.new(line_type)
57
+ end
58
+
59
+ def parse(line)
60
+ Parse.new(line)
61
+ end
62
+
63
+ end
@@ -27,6 +27,12 @@ class TestingFormat < RequestLogAnalyzer::FileFormat::Base
27
27
  line.captures = [{ :name => :request_no, :type => :integer }]
28
28
  end
29
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
+
30
36
  report do |analyze|
31
37
  analyze.frequency :test_capture, :title => 'What is testing exactly?'
32
38
  end
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format specdoc
3
+ --reverse
@@ -2,14 +2,23 @@ $:.reject! { |e| e.include? 'TextMate' }
2
2
  $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
3
 
4
4
  require 'rubygems'
5
- require 'spec'
5
+ require 'spec/autorun'
6
6
  require 'request_log_analyzer'
7
7
 
8
8
  module RequestLogAnalyzer::Spec
9
9
  end
10
10
 
11
- require File.dirname(__FILE__) + '/lib/testing_format'
12
- require File.dirname(__FILE__) + '/lib/mocks'
13
- require File.dirname(__FILE__) + '/lib/helper'
11
+ # Include all files in the spec_helper directory
12
+ Dir[File.dirname(__FILE__) + "/lib/**/*.rb"].each do |file|
13
+ require file
14
+ end
14
15
 
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,208 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Aggregator::Database do
4
+
5
+ before(:each) do
6
+ log_parser = RequestLogAnalyzer::Source::LogParser.new(testing_format)
7
+ @database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database => ':memory:')
8
+
9
+ @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, { :regexp => /Testing (\w+), tries\: (\d+)/,
10
+ :captures => [{ :name => :what, :type => :string }, { :name => :tries, :type => :integer },
11
+ { :name => :evaluated, :type => :hash, :provides => {:evaluated_field => :duration} }]})
12
+ end
13
+
14
+ # The prepare method is called before the parsing starts. It should establish a connection
15
+ # to a database that is suitable for inserting requests later on.
16
+ describe '#prepare' do
17
+
18
+ before(:each) do
19
+ @database_inserter.stub!(:establish_database_connection!)
20
+ @database_inserter.stub!(:create_database_schema!)
21
+ end
22
+
23
+ it 'should establish the database connection' do
24
+ @database_inserter.should_receive(:establish_database_connection!)
25
+ @database_inserter.prepare
26
+ end
27
+
28
+ it 'should create the database schema during preparation' do
29
+ @database_inserter.should_receive(:create_database_schema!)
30
+ @database_inserter.prepare
31
+ end
32
+ end
33
+
34
+ # The create_database_table method should create a database table according to the line definition,
35
+ # so that parsed lines can be stored in it later on.
36
+ describe '#create_database_table' do
37
+
38
+ before(:each) do
39
+ @table_creator = mock('create_table block')
40
+ @table_creator.stub!(:column)
41
+ ActiveRecord::Migration.stub!(:create_table).and_yield(@table_creator)
42
+ ActiveRecord::Migration.stub!(:add_index)
43
+ end
44
+
45
+ it "should create a table based on the line type name" do
46
+ ActiveRecord::Migration.should_receive(:create_table).with('test_lines')
47
+ @database_inserter.send(:create_database_table, @line_definition)
48
+ end
49
+
50
+ it "should create an index on the request_id field" do
51
+ ActiveRecord::Migration.should_receive(:add_index).with('test_lines', [:request_id])
52
+ @database_inserter.send(:create_database_table, @line_definition)
53
+ end
54
+
55
+ it "should create a request_id field to link the requests together" do
56
+ @table_creator.should_receive(:column).with(:request_id, :integer)
57
+ @database_inserter.send(:create_database_table, @line_definition)
58
+ end
59
+
60
+ it "should create a lineno field to save the location of the line in the original file" do
61
+ @table_creator.should_receive(:column).with(:lineno, :integer)
62
+ @database_inserter.send(:create_database_table, @line_definition)
63
+ end
64
+
65
+ it "should create a field of the correct type for every defined field" do
66
+ @table_creator.should_receive(:column).with(:what, :string)
67
+ @table_creator.should_receive(:column).with(:tries, :integer)
68
+ # :hash capture type should map on a :text field type
69
+ @table_creator.should_receive(:column).with(:evaluated, :text)
70
+ @database_inserter.send(:create_database_table, @line_definition)
71
+ end
72
+
73
+ it "should create a field of the correct type for every provided field" do
74
+ # :duration capture type should map on a :double field type
75
+ @table_creator.should_receive(:column).with(:evaluated_field, :double)
76
+ @database_inserter.send(:create_database_table, @line_definition)
77
+ end
78
+ end
79
+
80
+ describe '#create_activerecord_class' do
81
+ before(:each) do
82
+ # Make sure the ORM module exists
83
+ @database_inserter.send(:initialize_orm_module!)
84
+
85
+ # Mockthe request ORM class
86
+ @request_class = mock('Request ActiveRecord::Base class')
87
+ @request_class.stub!(:has_many)
88
+ @database_inserter.stub!(:request_class).and_return(@request_class)
89
+ end
90
+
91
+ it "should register the constant for the line type AR class" do
92
+ @database_inserter.send(:create_activerecord_class, @line_definition)
93
+ @database_inserter.orm_module.const_defined?('TestLine').should be_true
94
+ end
95
+
96
+ it "should create a class that inherits from ActiveRecord::Base and the base class of the ORM module" do
97
+ @database_inserter.send(:create_activerecord_class, @line_definition)
98
+ @database_inserter.orm_module.const_get('TestLine').ancestors.should include(ActiveRecord::Base, @database_inserter.orm_module::Base)
99
+ end
100
+
101
+ describe 'defining the new ORM class' do
102
+ before(:each) do
103
+ # Mock the newly created ORM class for the test_line
104
+ @orm_class = mock('Line ActiveRecord::Base class')
105
+ @orm_class.stub!(:belongs_to)
106
+ @orm_class.stub!(:serialize)
107
+ Class.stub!(:new).and_return(@orm_class)
108
+ end
109
+
110
+ it "should create a has_many relation on the Request class" do
111
+ @request_class.should_receive(:has_many).with(:test_lines)
112
+ @database_inserter.send(:create_activerecord_class, @line_definition)
113
+ end
114
+
115
+ it "should create a belongs_to relation to the request class" do
116
+ @orm_class.should_receive(:belongs_to).with(:request)
117
+ @database_inserter.send(:create_activerecord_class, @line_definition)
118
+ end
119
+
120
+ it "should serialize the :evaluate field into the database" do
121
+ @orm_class.should_receive(:serialize).with(:evaluated, Hash)
122
+ @database_inserter.send(:create_activerecord_class, @line_definition)
123
+ end
124
+ end
125
+ end
126
+
127
+ describe '#create_database_schema!' do
128
+
129
+ before(:each) do
130
+ @line_type_cnt = testing_format.line_definitions.length
131
+
132
+ #Make sure no actual migrations occur
133
+ ActiveRecord::Migration.stub!(:add_index)
134
+ ActiveRecord::Migration.stub!(:create_table)
135
+
136
+ # Stub the expected method calls for the preparation
137
+ @database_inserter.stub!(:create_database_table)
138
+ @database_inserter.stub!(:create_activerecord_class)
139
+
140
+
141
+ # Make sure the ORM module exists
142
+ @database_inserter.send(:initialize_orm_module!)
143
+ end
144
+
145
+ it "should create a requests table to join request lines" do
146
+ ActiveRecord::Migration.should_receive(:create_table).with("requests")
147
+ @database_inserter.send :create_database_schema!
148
+ end
149
+
150
+ it "should create a Request class inheriting from ActiveRecord and the base class of the ORM module" do
151
+ @database_inserter.send :create_database_schema!
152
+ @database_inserter.request_class.ancestors.should include(ActiveRecord::Base, @database_inserter.orm_module::Base)
153
+ end
154
+
155
+ it "should create a warnings table for logging parse warnings" do
156
+ ActiveRecord::Migration.should_receive(:create_table).with("warnings")
157
+ @database_inserter.send :create_database_schema!
158
+ end
159
+
160
+ it "should create a Warnng class inheriting from ActiveRecord and the base class of the ORM module" do
161
+ @database_inserter.send :create_database_schema!
162
+ @database_inserter.warning_class.ancestors.should include(ActiveRecord::Base, @database_inserter.orm_module::Base)
163
+ end
164
+
165
+ it "should create a table for every line type" do
166
+ @database_inserter.should_receive(:create_database_table).with(an_instance_of(RequestLogAnalyzer::LineDefinition)).exactly(@line_type_cnt).times
167
+ @database_inserter.send :create_database_schema!
168
+ end
169
+
170
+ it "should create a ORM for every line type" do
171
+ @database_inserter.should_receive(:create_activerecord_class).with(an_instance_of(RequestLogAnalyzer::LineDefinition)).exactly(@line_type_cnt).times
172
+ @database_inserter.send :create_database_schema!
173
+ end
174
+ end
175
+
176
+ describe '#aggregate' do
177
+ before(:each) do
178
+ @database_inserter.prepare
179
+
180
+ @incomplete_request = testing_format.request( {:line_type => :first, :request_no => 564})
181
+ @completed_request = testing_format.request( {:line_type => :first, :request_no => 564},
182
+ {:line_type => :test, :test_capture => "awesome"},
183
+ {:line_type => :test, :test_capture => "indeed"},
184
+ {:line_type => :eval, :evaluated => { :greating => 'howdy'}, :greating => 'howdy' },
185
+ {:line_type => :last, :request_no => 564})
186
+ end
187
+
188
+ it "should insert a record in the request table" do
189
+ TestingFormat::Database::Request.count.should == 0
190
+ @database_inserter.aggregate(@incomplete_request)
191
+ TestingFormat::Database::Request.count.should == 1
192
+ end
193
+
194
+ it "should insert records in all relevant line tables" do
195
+ @database_inserter.aggregate(@completed_request)
196
+ request = TestingFormat::Database::Request.first
197
+ request.should have(2).test_lines
198
+ request.should have(1).first_lines
199
+ request.should have(1).eval_lines
200
+ request.should have(1).last_lines
201
+ end
202
+
203
+ it "should log a warning in the warnings table" do
204
+ TestingFormat::Database::Warning.should_receive(:create!).with(hash_including(:warning_type => 'test_warning'))
205
+ @database_inserter.warning(:test_warning, "Testing the warning system", 12)
206
+ end
207
+ end
208
+ end