assert_efficient_sql 0.3.0 → 0.3.1

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.
@@ -8,7 +8,7 @@ require 'assert2'
8
8
  # ERGO check for valid options
9
9
  # ERGO highlite the offending row in the analysis
10
10
  # ERGO cite http://hackmysql.com/selectandsort
11
- # ERGO link from http://efficient-sql.rubyforge.org/files/README.html to
11
+ # ERGO link from http://efficient-sql.rubyforge.org/files/README.html to
12
12
  # project page
13
13
  # ERGO hamachi.cc
14
14
  # ERGO is flunk susceptible to <?> bug?
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
 
61
61
  # TODO use extended?
62
62
  # http://www.mysqlperformanceblog.com/2006/07/24/extended-explain/
63
- #
63
+ #
64
64
  # ERGO p select_without_analyzer('show warnings')
65
65
  # hence, show warnings caused by your queries, too
66
66
 
@@ -73,7 +73,7 @@ module ActiveRecord
73
73
  end
74
74
  ) if sql =~ /^select/i
75
75
  end
76
-
76
+
77
77
  explained = [sql, name, analysis.map(&:with_indifferent_access)]
78
78
  (@analyses ||= []) << explained
79
79
  end
@@ -87,14 +87,14 @@ end
87
87
  module AssertEfficientSql; end
88
88
 
89
89
  class AssertEfficientSql::SqlEfficiencyAsserter
90
-
90
+
91
91
  def initialize(options, context)
92
92
  @issues = []
93
93
  @options, @context = options, context
94
94
  @analyses = ActiveRecord::Base.connection.analyses
95
95
  @session_before = fetch_database_session
96
96
  yield
97
- check_for_query_statements
97
+ check_for_query_statements
98
98
  @session_after = fetch_database_session
99
99
  check_session_status
100
100
 
@@ -103,7 +103,7 @@ class AssertEfficientSql::SqlEfficiencyAsserter
103
103
  analyze_efficiency
104
104
  end
105
105
  end
106
-
106
+
107
107
  puts explain_all if @options[:verbose]
108
108
  end
109
109
 
@@ -138,27 +138,27 @@ class AssertEfficientSql::SqlEfficiencyAsserter
138
138
  end
139
139
  end
140
140
  end
141
-
141
+
142
142
  def analyze_efficiency
143
143
  rows = @explanation[:rows].to_i
144
144
  throttle = @options[:throttle]
145
-
145
+
146
146
  check rows <= throttle do
147
147
  "row count #{ rows } is more than :throttle => #{ throttle }"
148
148
  end
149
-
150
- check @options[:ALL] || 'ALL' != @explanation[:type] do
151
- 'full table scan'
149
+
150
+ check @options[:ALL] || 'ALL' != @explanation[:type] do
151
+ 'full table scan'
152
152
  end
153
-
153
+
154
154
  check @options[:Using_filesort] ||
155
155
  @explanation[:Extra] !~ /(Using filesort)/ do
156
156
  $1
157
157
  end
158
-
158
+
159
159
  flunk 'Pessimistic ' + format_explanation unless @issues.empty?
160
160
  end
161
-
161
+
162
162
  def check_for_query_statements
163
163
  flunk 'assert_efficient_sql saw no queries!' if @analyses.empty?
164
164
  end
@@ -166,35 +166,37 @@ class AssertEfficientSql::SqlEfficiencyAsserter
166
166
  def flunk(why)
167
167
  @context.flunk @context.build_message(@options[:diagnostic], why)
168
168
  end
169
-
169
+
170
170
  def format_explanation
171
171
  @name = 'for ' + @name unless @name.blank?
172
172
 
173
173
  return "\nquery #{ @name }\n" +
174
174
  @issues.join("\n") +
175
- "\n#{ @sql }\n " +
175
+ "\n#{ @sql }\n " +
176
176
  @analysis.qa_columnized
177
177
  end
178
178
 
179
179
  end
180
-
180
+
181
181
  #:startdoc:
182
182
 
183
183
 
184
184
  module AssertEfficientSql
185
-
186
- # See: http://www.oreillynet.com/onlamp/blog/2007/07/assert_latest_and_greatest.html
187
-
185
+
186
+ # This collects every model in the given class that appears while its block runs
188
187
  def assert_latest(*models, &block)
189
188
  models, diagnostic = _get_latest_args(models, 'assert')
190
- get_latest(models, &block) or _flunk_latest(models, diagnostic, true, block)
189
+ latests = get_latest(models, &block)
190
+ latests.compact.length == models.length or
191
+ _flunk_latest(models, latests, diagnostic, true, block)
192
+ return *latests
191
193
  end
192
194
 
193
- def _get_latest_args(models, what)
195
+ def _get_latest_args(models, what) #:nodoc:
194
196
  diagnostic = nil
195
197
  diagnostic = models.pop if models.last.kind_of? String
196
-
197
- unless models.length > 0 and
198
+
199
+ unless models.length > 0 and
198
200
  (diagnostic.nil? or diagnostic.kind_of? String)
199
201
  raise "call #{ what }_latest(models..., diagnostic) with any number " +
200
202
  'of Model classes, followed by an optional diagnostic message'
@@ -202,49 +204,55 @@ module AssertEfficientSql
202
204
  return models, diagnostic
203
205
  end
204
206
  private :_get_latest_args
205
-
207
+
206
208
  def deny_latest(*models, &block)
207
209
  models, diagnostic = _get_latest_args(models, 'deny')
208
- return unless got = get_latest(models, &block)
209
- models = [got].flatten.compact.map(&:class)
210
- _flunk_latest(models, diagnostic, false, block)
210
+ latests = get_latest(models, &block)
211
+ return if latests.compact.empty?
212
+ models = [latests].flatten.compact.map(&:class)
213
+ _flunk_latest(models, latests, diagnostic, false, block)
211
214
  end
212
215
 
213
216
  def get_latest(models, &block)
214
217
  max_ids = models.map{|model| model.maximum(:id) || 0 }
215
218
  block.call
216
219
  index = -1
217
- return *models.map{|model|
220
+ return models.map{|model|
218
221
  all = *model.find( :all,
219
222
  :conditions => "id > #{max_ids[index += 1]}",
220
223
  :order => "id asc" )
221
- all # * returns nil for [],
222
- # one object for [x],
224
+ all # * returns nil for [],
225
+ # one object for [x],
223
226
  # or an array with more than one item
224
227
  }
225
228
  end
226
-
227
- def _flunk_latest(models, diagnostic, polarity, block)
228
- model_names = models.map(&:name).join(', ')
229
- rationale = "should#{ ' not' unless polarity
229
+
230
+ def _flunk_latest(models, latests, diagnostic, polarity, block) #:nodoc:
231
+ model_names = models.map(&:name)
232
+ model_names.each_with_index do |it, index|
233
+ model_names[index] = nil if !!latests[index] == polarity
234
+ end
235
+ model_names = model_names.compact.join(', ')
236
+ rationale = "should#{ ' not' unless polarity
230
237
  } create new #{ model_names
231
- } record(s) in block:\n\t\t#{
232
- reflect_source(&block).gsub("\n", "\n\t\t")
238
+ } record(s) in block:\n\t\t#{
239
+ reflect_source(&block).gsub("\n", "\n\t\t")
233
240
  }\n"
234
241
  # RubyNodeReflector::RubyReflector.new(block, false).result }"
235
242
  # note we don't evaluate...
236
243
  flunk build_message(diagnostic, rationale)
237
244
  end
238
245
  private :_flunk_latest
239
-
240
-
246
+
247
+
241
248
  def _exec(cmd) #:nodoc:
242
249
  ActiveRecord::Base.connection.execute(cmd)
243
250
  end
244
251
 
252
+ # See: http://www.oreillynet.com/onlamp/blog/2007/07/assert_latest_and_greatest.html
245
253
  def assert_efficient_sql(options = {}, &block)
246
254
  options = { :verbose => true } if options == :verbose
247
-
255
+
248
256
  if options.class == Hash
249
257
  options.reverse_merge! default_options
250
258
 
@@ -257,13 +265,83 @@ module AssertEfficientSql
257
265
  else
258
266
  print_syntax
259
267
  end
260
-
268
+
261
269
  return []
262
270
  end
263
271
 
272
+ def self._singulars #:nodoc:
273
+ return [ [:ids, :id ], [:key_lens, :key_len ],
274
+ [:keys, :key ], [:refs, :ref ],
275
+ [:rows, :rows ], [:select_types, :select_type],
276
+ [:tables, :table ], [:types, :type ] ]
277
+ end
278
+
279
+ def self._multiples #:nodoc:
280
+ return [ [:extras, :Extra ],
281
+ [:possible_keys, :possible_keys] ]
282
+ end
283
+
284
+ # def test_order_transactions_by_id
285
+ # sqls = inspect_sql :match => /SELECT conversations.\*/ do
286
+ # get_sales_after_transaction_id
287
+ # end
288
+ # statement = sqls.first[:statement]
289
+ # assert{ statement =~ /ORDER BY id asc/ }
290
+ # end
291
+ #
292
+ def inspect_sql(options = {}, &block)
293
+ verbose = options.fetch(:verbose, false)
294
+ match = options.fetch(:match, /./)
295
+ args = { :ALL => true, :Using_filesort => true, :verbose => verbose }
296
+ inspections = assert_efficient_sql(args, &block)
297
+ # pp inspections.first
298
+
299
+ explains = inspections.inject([]) do |a, inspection|
300
+ if inspection.first =~ match
301
+ explanations = inspection.last
302
+
303
+ h = { :statement => inspection.first,
304
+ :explanations => explanations }
305
+
306
+ AssertEfficientSql._singulars.each do |rollup, explainer|
307
+ h[rollup] = explanations.map{|e| q = e[explainer] and q.to_sym }
308
+ end
309
+
310
+ AssertEfficientSql._multiples.each do |rollup, explainer|
311
+ h[rollup] = explanations.map do |e|
312
+ q = e[explainer]
313
+ [q].flatten.map do |e|
314
+ e and
315
+ e.any? and
316
+ (rollup == :extras ? e.gsub!(' ', '_') : e ) and
317
+ e.to_sym
318
+ end
319
+ end
320
+ end
321
+ a << h
322
+ end
323
+ a
324
+ end
325
+ return AssertEfficientSql._decorate_explainers(explains)
326
+ end
327
+
328
+ def self._decorate_explainers(explains) #:nodoc:
329
+ def explains.find_statements(regexp)
330
+ return AssertEfficientSql._decorate_explainers(select{|explanation|
331
+ explanation[:statement] =~ regexp
332
+ })
333
+ end
334
+
335
+ (_multiples + _singulars).each do |rollup, explainer|
336
+ eval "def explains.#{rollup}; map{|q|q[:#{rollup}]}.flatten.uniq; end"
337
+ end
338
+
339
+ return explains
340
+ end # FIXME gotta be private
341
+
264
342
  class BufferStdout #:nodoc:
265
343
  def write(stuff)
266
- (@output ||= '') << stuff
344
+ (@output ||= '') << stuff
267
345
  end
268
346
  def output; @output || '' end
269
347
  end
@@ -284,19 +362,19 @@ module AssertEfficientSql
284
362
  end
285
363
 
286
364
  private
287
-
365
+
288
366
  def current_adapter?(type) #:nodoc:
289
367
  ActiveRecord::ConnectionAdapters.const_defined?(type) and
290
368
  ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
291
369
  end
292
-
370
+
293
371
  def warn_adapter_required(options)
294
372
  if options[:warn_mysql_required]
295
373
  puts 'assert_efficient_sql requires MySQL' unless $warned_once
296
374
  $warned_once = true
297
- end
375
+ end
298
376
  end
299
-
377
+
300
378
  def assert_efficient_mysql(options, &block)
301
379
  outer_block_analyses = ActiveRecord::Base.connection.analyses
302
380
  ActiveRecord::Base.connection.analyses = []
@@ -306,7 +384,7 @@ module AssertEfficientSql
306
384
  ensure
307
385
  ActiveRecord::Base.connection.analyses = outer_block_analyses
308
386
  end
309
-
387
+
310
388
  def syntax
311
389
  return {
312
390
  :diagnostic => [nil , 'supplementary message in failure reports'],
@@ -316,22 +394,22 @@ module AssertEfficientSql
316
394
  :verbose => [false, 'if the test passes, print the EXPLAIN'],
317
395
  :warn_mysql_required => [true , 'disable the spew advising we only work with MySQL'] }
318
396
  end
319
-
397
+
320
398
  def default_options
321
399
  options = syntax.dup
322
400
  options.each{|k,(v,m)| options[k] = v}
323
401
  return options
324
402
  end
325
-
403
+
326
404
  def print_syntax
327
405
  puts "\n\nassert_efficient_sql called with invalid argument.\n"
328
406
  puts " __flag__ __default__ __effect__"
329
-
407
+
330
408
  syntax.each do |k,(v,m)|
331
409
  printf " :%-14s => %-8s # %s\n", k, v.inspect, m
332
410
  end
333
411
  end
334
-
412
+
335
413
  end
336
414
 
337
415
  #:stopdoc:
@@ -0,0 +1,384 @@
1
+
2
+ #:stopdoc:
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require 'active_record/connection_adapters/mysql_adapter.rb'
7
+ require 'test/unit'
8
+ require 'pathname'
9
+ require 'yaml'
10
+ require 'optparse'
11
+ require 'erb'
12
+ Assert_efficient_sql_root = (Pathname.new(__FILE__).expand_path.dirname + '..').expand_path
13
+ load Assert_efficient_sql_root + 'lib/assert_efficient_sql.rb'
14
+
15
+ # ERGO even work if you are sqLite3
16
+ class Foo < ActiveRecord::Base; end
17
+ class T2 < ActiveRecord::Base; self.table_name ='t2'; end
18
+
19
+ #:startdoc:
20
+
21
+ class AssertEfficientSqlTest < Test::Unit::TestCase
22
+
23
+ def setup #:nodoc:
24
+ config_file = (Assert_efficient_sql_root + 'config/database.yml').to_s
25
+ config = YAML.load(ERB.new(IO.read(config_file)).result)['test']
26
+ ActiveRecord::Base.establish_connection(config)
27
+
28
+ ActiveRecord::Base.connection.
29
+ create_table(:foos) do |t|
30
+ t.column :name, :string, :limit => 60
31
+ end
32
+
33
+ 43.times{ Foo.create!(:name => 'whatever') }
34
+ _exec %[ CREATE TABLE `t2` (
35
+ `id` int(11) auto_increment,
36
+ `a` int(11) NOT NULL DEFAULT '0',
37
+ `b` blob,
38
+ `c` int(11) NOT NULL,
39
+ PRIMARY KEY (id)
40
+ ) ENGINE=InnoDB ] rescue nil
41
+ #system('mysql -u root beast_test')
42
+ end
43
+
44
+ # If +assert_efficient_sql+ (generally) dislikes your arguments,
45
+ # it will print out its default options, each with an explanation
46
+ #
47
+ def test_help
48
+ assert_stdout /invalid.*argument.*
49
+ verbose .* =\> .* false/mx do
50
+ assert_efficient_sql(:help){}
51
+ end
52
+ end
53
+
54
+ def test_assert_efficient_sql
55
+ assert_efficient_sql{ Foo.find(2) }
56
+ end
57
+
58
+ # If your SQL is already efficient, use <b>:verbose</b> to diagnose
59
+ # <i>why</i> it's efficient.
60
+ #
61
+ def test_verbose
62
+ assert_stdout /select_type/ do
63
+ assert_efficient_sql :verbose do
64
+ Foo.find_by_id(42)
65
+ end
66
+ end
67
+ end
68
+
69
+ # If your block did not call any SQL SELECT statements,
70
+ # you probably need a warning!
71
+ #
72
+ def test_require_sql
73
+ assert_flunked /no queries/ do
74
+ assert_efficient_sql{}
75
+ end
76
+ end
77
+
78
+ # This test case uses
79
+ # <code>assert_raise_message[http://www.oreillynet.com/onlamp/blog/2007/07/assert_raise_on_ruby_dont_just.html]</code>
80
+ # to demonstrate <code>assert_efficient_sql</code> failing:
81
+ #
82
+ def test_assert_inefficient_sql
83
+ assert_flunked /Pessimistic.*
84
+ full.table.scan.*
85
+ Foo.Load/mx do
86
+ assert_efficient_sql do
87
+ Foo.find_by_sql('select * from foos a')
88
+ end
89
+ end
90
+ end
91
+
92
+ # One common pessimization is a query that reads thousands
93
+ # of rows just to return a few. +assert_efficient_sql+
94
+ # counts the rows hit in each phase of an SQL +SELECT+,
95
+ # and faults if any row count exceeds <b>1,000</b>.
96
+ #
97
+ # Adjust this count with <b><code>:throttle => 42</code></b>.
98
+ #
99
+ def test_throttle
100
+ 101.times{|x| Foo.create :name => "foo_#{ x }" }
101
+
102
+ assert_flunked /Pessimistic.*
103
+ more.than.*100.*
104
+ Foo.Load/mx do
105
+ assert_efficient_sql :throttle => 100, :ALL => true do
106
+ Foo.find(:all)
107
+ end
108
+ end
109
+ end
110
+
111
+ # Sometimes you need an +ALL+, even while other <code>assert_efficient_sql</code>
112
+ # checks must pass. To positively declare we like +ALL+, pass it as the key of a
113
+ # +true+ option into the assertion:
114
+ #
115
+ def test_assert_all
116
+ assert_efficient_sql :ALL => true do
117
+ Foo.find(:all)
118
+ end
119
+ end
120
+
121
+ # If your +WHERE+ and +ORDER+ clauses are too complex,
122
+ # MySQL might need to write a file (or worse), just to
123
+ # satisfy a query. +assert_efficient_sql+ detects this
124
+ # pernicious situation:
125
+ #
126
+ def test_prevent_filesorts
127
+ _exec %[ CREATE TABLE `t1` (
128
+ `a` int(11) NOT NULL DEFAULT '0',
129
+ `b` blob NOT NULL,
130
+ `c` text NOT NULL,
131
+ PRIMARY KEY (`a`,`b`(255),`c`(255)),
132
+ KEY `t1ba` (`b`(10),`a`)
133
+ ) ENGINE=InnoDB ]
134
+
135
+ assert_flunked /Using.filesort/ do
136
+ assert_efficient_sql do
137
+ Foo.find_by_sql('SELECT a FROM t1 ORDER BY b')
138
+ end
139
+ end
140
+ ensure
141
+ _exec 'drop table t1' # ERGO if exist
142
+ end
143
+
144
+ def test_ALL_with_possible_keys
145
+ assert_efficient_sql :ALL => true, :Using_filesort => true, :key => false do
146
+ Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
147
+ end
148
+ end
149
+
150
+ # <code>assert_efficient_sql</code> calls
151
+ # <code>SHOW SESSION STATUS</code> before and after
152
+ # its sampled block. If you need to prevent an advanced
153
+ # pessimization, such as <code>Created_tmp_disk_tables</code>,
154
+ # pass <code>:Created_tmp_disk_tables => 0</code>. The
155
+ # assertion will compare difference in STATUS before
156
+ # and after calling its block. A difference greater
157
+ # than the allowed difference will trigger a fault.
158
+ #
159
+ # To test this, we simply detect a STATUS variable
160
+ # which is not a warning.
161
+ #
162
+ def test_declare_futile_war_on_Innodb_rows_read
163
+ assert_flunked /just.for.test.*
164
+ Innodb_rows_read/mx do
165
+ assert_efficient_sql :diagnostic => 'just for test!',
166
+ :Innodb_rows_read => 0 do
167
+ Foo.find(:all)
168
+ end
169
+ end
170
+ end
171
+
172
+ # You can also nest the assertion, to provide different
173
+ # options for different blocks. The assertion allows
174
+ # this because your test might also have some other
175
+ # reason to use blocks
176
+ #
177
+ def test_nest
178
+ outer_result = assert_efficient_sql do
179
+ inner_result = assert_efficient_sql :ALL => true do
180
+ Foo.find(:all)
181
+ end
182
+
183
+ assert_no_match /where/i, inner_result[0][0]
184
+ Foo.find(42)
185
+ end
186
+ assert_match /where/i, outer_result[0][0]
187
+ end
188
+
189
+ def test_inspect_sql
190
+ inspections = inspect_sql do
191
+ Foo.find_by_sql('SELECT id FROM t2 ORDER BY c')
192
+ Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
193
+ Foo.find_by_sql('SELECT * FROM foos, t2')
194
+ end
195
+ assert{ inspections.length == 3 }
196
+ assert{ inspections[0][:statement] == 'SELECT id FROM t2 ORDER BY c' }
197
+ assert{ inspections[0][:possible_keys] == [[nil]] }
198
+ assert{ inspections[0][:extras] == [[:Using_filesort]] }
199
+ assert{ inspections[0][:types] == [:ALL] }
200
+ assert{ inspections[1][:tables] == [:t2] }
201
+ assert{ inspections[1][:possible_keys] == [[nil]] }
202
+ assert{ inspections[2][:tables] == [:t2, :foos] } # note the order is constrained!
203
+ assert{ inspections[2][:possible_keys] == [[nil], [nil]] }
204
+ assert{ inspections[1][:keys] == [nil] } # [:PRIMARY] }
205
+ assert{ inspections[2][:keys] == [nil, nil] }
206
+ end
207
+
208
+ def test_inspect_sql_explanations
209
+ inspections = inspect_sql do
210
+ Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
211
+ Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
212
+ Foo.find_by_sql('SELECT * FROM foos, t2')
213
+ end
214
+ assert{ inspections.length == 3 }
215
+ assert{ inspections[0][:statement] == 'SELECT a FROM t2 ORDER BY c' }
216
+ assert{ inspections[0][:explanations].first[:possible_keys] == nil }
217
+ assert{ inspections[0][:explanations].first[:Extra] == 'Using_filesort' }
218
+ assert{ inspections[0][:explanations].first[:type] == 'ALL' }
219
+ assert{ inspections[1][:explanations].first[:table] == 't2' }
220
+ assert{ inspections[1][:explanations].first[:possible_keys] == nil }
221
+ assert{ inspections[2][:explanations].first[:table] == 't2' } # note the order is constrained!
222
+ assert{ inspections[1][:explanations].first[:possible_keys] == nil }
223
+ assert{ inspections[2][:explanations].first[:possible_keys] == nil }
224
+ end
225
+
226
+ def test_inspect_sql_find_statements
227
+ inspections = inspect_sql do
228
+ Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
229
+ Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
230
+ Foo.find_by_sql('SELECT * FROM foos, t2')
231
+ end
232
+ inspections = inspections.find_statements(/from t2/i)
233
+ assert{ inspections[0][:statement] == 'SELECT a FROM t2 ORDER BY c' }
234
+ assert{ inspections[1][:explanations].first[:table] == 't2' }
235
+ deny{ inspections[2] }
236
+ assert{ inspections.tables == [:t2] }
237
+ #assert{ inspections.keys.to_set == [nil, :PRIMARY].to_set }
238
+ end
239
+
240
+ def test_inspect_certain_sql_statements
241
+ inspections = inspect_sql :match => /from t2/i do
242
+ Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
243
+ Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
244
+ Foo.find_by_sql('SELECT * FROM foos, t2')
245
+ end
246
+ assert{ inspections[0][:statement] == 'SELECT a FROM t2 ORDER BY c' }
247
+ assert{ inspections[1][:explanations].first[:table] == 't2' }
248
+ deny{ inspections[2] }
249
+ end
250
+
251
+ def test_inspect_sql_rollups
252
+ inspections = inspect_sql :verbose => false do
253
+ Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
254
+ Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
255
+ Foo.find_by_sql('SELECT * FROM foos, t2')
256
+ end
257
+ assert{ inspections.tables.to_set == [:t2, :foos].to_set }
258
+ assert{ inspections.keys.to_set == [nil].to_set }
259
+ end
260
+
261
+ def test_assert_latest
262
+ assert_flunked(/false/){ assert false }
263
+
264
+ assert_flunked /should create new Foo record\(s\) in/ do
265
+ assert_latest(Foo){ 'nada!' }
266
+ end
267
+
268
+ assert_flunked /because.*should not create new Foo record\(s\) in/m do
269
+ deny_latest Foo, 'because' do
270
+ Foo.create
271
+ end
272
+ end
273
+ nada = 42
274
+
275
+ assert_flunked /in block:\n.*nada/m do
276
+ assert_latest(Foo){ nada }
277
+ end
278
+
279
+ deny_flunked /42/ do # assert_latest shall not evaluate thy block
280
+ assert_latest(Foo){ nada }
281
+ end
282
+ end
283
+
284
+ def test_assert_polygamous_latest
285
+ f, t = assert_latest Foo, T2, 'diagnostic' do
286
+ Foo.create
287
+ T2.create(:c => 42)
288
+ end
289
+
290
+ assert{ f.id > 0 and t.id > 0 }
291
+
292
+ f1, f2 = assert_latest Foo do
293
+ 2.times{ Foo.create }
294
+ end
295
+
296
+ assert 'items return ordered by id' do
297
+ f1.id > 0 and f2.id > f1.id
298
+ end
299
+
300
+ t, fz = assert_latest T2, Foo do
301
+ T2.create(:c => 42)
302
+ 2.times{ Foo.create }
303
+ end
304
+
305
+ assert do
306
+ t.id > 0 and
307
+ fz[0].id > 0 and
308
+ fz[1].id > fz[0].id
309
+ end
310
+
311
+ t, (f1, f2) = assert_latest T2, Foo do
312
+ T2.create(:c => 42)
313
+ 2.times{ Foo.create }
314
+ end
315
+
316
+ assert do
317
+ t.id > 0 and
318
+ f1.id > 0 and
319
+ f2.id > f1.id
320
+ end
321
+ end
322
+
323
+
324
+ def test_assert_polygamous_latest_fails
325
+ assert_flunked /diagnostic.*new T2 rcord/m do
326
+ f, t = assert_latest Foo, T2, 'diagnostic' do
327
+ Foo.create
328
+ end
329
+ end
330
+ end
331
+
332
+ def test_deny_polygamous_latest_fails
333
+ assert_flunked /diagnostic.*new Foo record/m do
334
+ f, t = deny_latest Foo, T2, 'diagnostic' do
335
+ Foo.create
336
+ end
337
+ end
338
+ end
339
+
340
+ def teardown #:nodoc:
341
+ ActiveRecord::Base.connection.drop_table(:foos) rescue nil
342
+ ActiveRecord::Base.connection.drop_table(:t2) rescue nil
343
+ end
344
+
345
+ end
346
+
347
+ #:enddoc:
348
+
349
+ class AssertNonMysqlTest < Test::Unit::TestCase
350
+
351
+ # The assertion requires MySQL. To peacefully coexist with
352
+ # test rigs that use multiple adapters, we only warn, once,
353
+ # if MySQL is not found. If you don't need this warning,
354
+ # turn it off with :warn_mysql_required => false
355
+ #
356
+ def test_gently_forwarn_non_mysql_connectors
357
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3',
358
+ :dbfile => ':memory:' )
359
+
360
+ deny_stdout /requires MySQL/, 'disabled warning' do
361
+ assert_efficient_sql :warn_mysql_required => false
362
+ end
363
+
364
+ assert_stdout /requires MySQL/, 'warn the first time' do
365
+ assert_efficient_sql
366
+ end
367
+
368
+ deny_stdout /requires MySQL/, 'don\'t warn the second time' do
369
+ assert_efficient_sql
370
+ end
371
+
372
+ ensure
373
+ config_file = (Assert_efficient_sql_root + 'config/database.yml').to_s
374
+ config = YAML.load(ERB.new(IO.read(config_file)).result)['test']
375
+ ActiveRecord::Base.establish_connection(config)
376
+ end
377
+
378
+ end
379
+
380
+
381
+ def rails_root # ERGO is this used?
382
+ return (Pathname.new(__FILE__).expand_path.dirname + '../../../..').expand_path
383
+ end
384
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: assert_efficient_sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phlip
@@ -9,11 +9,12 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-03-12 00:00:00 -07:00
12
+ date: 2008-09-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: rubynode
16
+ name: assert2
17
+ type: :runtime
17
18
  version_requirement:
18
19
  version_requirements: !ruby/object:Gem::Requirement
19
20
  requirements:
@@ -22,7 +23,8 @@ dependencies:
22
23
  version: "0"
23
24
  version:
24
25
  - !ruby/object:Gem::Dependency
25
- name: assert2
26
+ name: rubynode
27
+ type: :runtime
26
28
  version_requirement:
27
29
  version_requirements: !ruby/object:Gem::Requirement
28
30
  requirements:
@@ -39,6 +41,7 @@ extensions: []
39
41
  extra_rdoc_files: []
40
42
 
41
43
  files:
44
+ - test/assert_efficient_sql_test.rb
42
45
  - lib/assert_efficient_sql.rb
43
46
  has_rdoc: false
44
47
  homepage: http://rubyforge.org/projects/efficient-sql
@@ -62,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
65
  requirements: []
63
66
 
64
67
  rubyforge_project: efficient-sql
65
- rubygems_version: 1.0.1
68
+ rubygems_version: 1.2.0
66
69
  signing_key:
67
70
  specification_version: 2
68
71
  summary: efficient assertions for ActiveRecord tests