Commit aac098d1a8fdc5c890fe50a5427945f400080eec

Authored by Luke Baker
1 parent 8b086743

rearrange question methods and dynamically create rake tasks

Showing 1 changed file with 133 additions and 119 deletions   Show diff stats
lib/tasks/test_api.rake
@@ -125,14 +125,11 @@ namespace :test_api do @@ -125,14 +125,11 @@ namespace :test_api do
125 end 125 end
126 126
127 127
128 - #catchup specific  
129 - if question.uses_catchup?  
130 - message, error_occurred = check_prompt_cache_hit_rate(question)  
131 - if error_occurred  
132 - errors << message  
133 - else  
134 - successes << message  
135 - end 128 + message, error_occurred = check_prompt_cache_hit_rate(question)
  129 + if error_occurred
  130 + errors << message
  131 + else
  132 + successes << message
136 end 133 end
137 134
138 end 135 end
@@ -303,155 +300,172 @@ namespace :test_api do @@ -303,155 +300,172 @@ namespace :test_api do
303 return error_message.blank? ? [success_message, false] : [error_message, true] 300 return error_message.blank? ? [success_message, false] : [error_message, true]
304 end 301 end
305 302
306 - def check_each_choice_appears_within_n_stddevs(question)  
307 - error_message =""  
308 - success_message = "Each choice has appeared n times, where n falls within 6 stddevs of the mean number of appearances for a question " +  
309 - "(Note: this applies only to seed choices (not user submitted) and choices currently marked active)"  
310 303
311 - wins_by_choice_id = question.votes.active.count(:group => :choice_id, :conditions => ["creator_id = ?", question.creator_id])  
312 - losses_by_choice_id= question.votes.active_loser.count(:group => :loser_choice_id, :conditions => ["creator_id = ?", question.creator_id]) 304 + namespace :question do
313 305
314 - #Rails returns an ordered hash, which doesn't allow for blocks to change merging logic.  
315 - #A little hack to create a normal hash  
316 - wins_hash = {}  
317 - wins_hash.merge!(wins_by_choice_id)  
318 - losses_hash = {}  
319 - losses_hash.merge!(losses_by_choice_id) 306 + # use this to dynamically create rake task for each question test
  307 + question_tasks = {
  308 + :answered_appearances_equals_votes_and_skips => "Ensure that a question has: answered_appearances == votes + skips",
  309 + :check_each_choice_appears_within_n_stddevs => "Ensure each choice appears within 6 standard deviations",
  310 + :check_each_choice_equally_likely_to_appear_left_or_right => "Ensure each choice is equally likely to appear on left or right",
  311 + :check_prompt_cache_hit_rate => "Check prompt cache hit rate",
  312 + :check_object_counter_cache_values_match_actual_values => "Check that object counter cache values match actual values"
  313 + }
  314 +
  315 + # dynamically create tasks for each question task
  316 + question_tasks.each do |taskname, description|
  317 + desc description
  318 + task taskname, [:question_id] => :environment do |t, args|
  319 + a = cleanup_args(args)
  320 + questions = Question.find(a[:question_id])
  321 + questions.each do |question|
  322 + # call task
  323 + puts send(taskname, question).inspect
  324 + end
  325 + end
  326 + end
320 327
  328 + def answered_appearances_equals_votes_and_skips(question)
  329 + error_message = ""
  330 + success_message = "All vote and skip objects have an associated appearance object"
  331 + skip_appearances_count = Appearance.count(
  332 + :conditions => ["skips.valid_record = 1 and appearances.question_id = ? AND answerable_id IS NOT NULL AND answerable_type = 'Skip'", question.id],
  333 + :joins => "LEFT JOIN skips ON (skips.id = appearances.answerable_id)")
  334 + vote_appearances_count = Appearance.count(
  335 + :conditions => ["votes.valid_record = 1 and appearances.question_id = ? AND answerable_id IS NOT NULL and answerable_type = 'Vote'", question.id],
  336 + :joins => "LEFT JOIN votes ON (votes.id = appearances.answerable_id)")
  337 + total_answered_appearances = skip_appearances_count + vote_appearances_count
  338 + total_votes = question.votes.count
  339 + total_skips = question.skips.count
  340 + if (total_answered_appearances != total_votes + total_skips)
  341 + error_message += "Question #{question.id}: answered_appearances = #{total_answered_appearances}, votes = #{total_votes}, skips = #{total_skips}"
  342 + end
321 343
322 344
323 - appearances_by_choice_id = wins_hash.merge(losses_hash) do |key, oldval, newval| oldval + newval end 345 + return error_message.blank? ? [success_message, false] : [error_message, true]
  346 + end
324 347
325 - sum = total_appearances = appearances_by_choice_id.values.inject(0) {|sum, x| sum +=x}  
326 - mean = average_appearances = total_appearances.to_f / appearances_by_choice_id.size.to_f 348 + def check_each_choice_appears_within_n_stddevs(question)
  349 + error_message =""
  350 + success_message = "Each choice has appeared n times, where n falls within 6 stddevs of the mean number of appearances for a question " +
  351 + "(Note: this applies only to seed choices (not user submitted) and choices currently marked active)"
327 352
328 - if sum > 0:  
329 - stddev = Math.sqrt( appearances_by_choice_id.values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / appearances_by_choice_id.size.to_f ) 353 + wins_by_choice_id = question.votes.active.count(:group => :choice_id, :conditions => ["creator_id = ?", question.creator_id])
  354 + losses_by_choice_id= question.votes.active_loser.count(:group => :loser_choice_id, :conditions => ["creator_id = ?", question.creator_id])
330 355
331 - # this choice appears to have been deactivated then reactivated after  
332 - # a period of voting  
333 - ignore_choices = [133189]  
334 - appearances_by_choice_id.each do |choice_id, n_i|  
335 - if ((n_i < (mean - 6*stddev)) || (n_i > mean + 6 *stddev)) && !ignore_choices.include?(choice_id) && Choice.find(choice_id).active?  
336 - error_message += "Choice #{choice_id} in Question ##{question.id} has an irregular number of appearances: #{n_i}, as compared to the mean: #{mean} and stddev #{stddev} for this question\n"  
337 - end  
338 - end  
339 - end 356 + #Rails returns an ordered hash, which doesn't allow for blocks to change merging logic.
  357 + #A little hack to create a normal hash
  358 + wins_hash = {}
  359 + wins_hash.merge!(wins_by_choice_id)
  360 + losses_hash = {}
  361 + losses_hash.merge!(losses_by_choice_id)
340 362
341 - return error_message.blank? ? [success_message, false] : [error_message, true]  
342 - end  
343 363
344 - def check_each_choice_equally_likely_to_appear_left_or_right(question)  
345 - error_message = ""  
346 - success_message = "All choices have equal probability of appearing on left or right (within error params)"  
347 - question.choices.each do |c|  
348 - left_prompts_ids = c.prompts_on_the_left.ids_only  
349 - right_prompts_ids = c.prompts_on_the_right.ids_only  
350 364
351 - left_appearances = question.appearances.count(:conditions => {:prompt_id => left_prompts_ids})  
352 - right_appearances = question.appearances.count(:conditions => {:prompt_id => right_prompts_ids}) 365 + appearances_by_choice_id = wins_hash.merge(losses_hash) do |key, oldval, newval| oldval + newval end
353 366
354 - n = left_appearances + right_appearances 367 + sum = total_appearances = appearances_by_choice_id.values.inject(0) {|sum, x| sum +=x}
  368 + mean = average_appearances = total_appearances.to_f / appearances_by_choice_id.size.to_f
355 369
356 - if n == 0  
357 - next  
358 - end  
359 - est_p = right_appearances.to_f / n.to_f  
360 - z = (est_p - 0.5).abs / Math.sqrt((0.5 * 0.5) / n.to_f) 370 + if sum > 0:
  371 + stddev = Math.sqrt( appearances_by_choice_id.values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / appearances_by_choice_id.size.to_f )
361 372
362 - if z > 6  
363 - error_message += "Error: Choice ID #{c.id} seems to favor one side: Left Appearances #{left_appearances}, Right Appearances: #{right_appearances}, z = #{z}\n" 373 + # this choice appears to have been deactivated then reactivated after
  374 + # a period of voting
  375 + ignore_choices = [133189]
  376 + appearances_by_choice_id.each do |choice_id, n_i|
  377 + if ((n_i < (mean - 6*stddev)) || (n_i > mean + 6 *stddev)) && !ignore_choices.include?(choice_id) && Choice.find(choice_id).active?
  378 + error_message += "Choice #{choice_id} in Question ##{question.id} has an irregular number of appearances: #{n_i}, as compared to the mean: #{mean} and stddev #{stddev} for this question\n"
  379 + end
  380 + end
364 end 381 end
  382 +
  383 + return error_message.blank? ? [success_message, false] : [error_message, true]
365 end 384 end
366 - return error_message.blank? ? [success_message, false] : [error_message, true]  
367 - end  
368 - def check_prompt_cache_hit_rate(question)  
369 - error_message = ""  
370 - success_message = "At least 90% of prompts on catchup algorithm questions were served from cache\n"  
371 385
372 - misses = question.get_prompt_cache_misses(Date.yesterday).to_i  
373 - hits = question.get_prompt_cache_hits(Date.yesterday).to_i 386 + def check_each_choice_equally_likely_to_appear_left_or_right(question)
  387 + error_message = ""
  388 + success_message = "All choices have equal probability of appearing on left or right (within error params)"
  389 + question.choices.each do |c|
  390 + left_prompts_ids = c.prompts_on_the_left.ids_only
  391 + right_prompts_ids = c.prompts_on_the_right.ids_only
374 392
375 - question.expire_prompt_cache_tracking_keys(Date.yesterday) 393 + left_appearances = question.appearances.count(:conditions => {:prompt_id => left_prompts_ids})
  394 + right_appearances = question.appearances.count(:conditions => {:prompt_id => right_prompts_ids})
376 395
377 - yesterday_appearances = question.appearances.count(:conditions => ['date(created_at) = ?', Date.yesterday]) 396 + n = left_appearances + right_appearances
378 397
379 - if misses + hits != yesterday_appearances  
380 - error_message += "Error! Question #{question.id} isn't tracking prompt cache hits and misses accurately! Expected #{yesterday_appearances}, Actual: #{misses+hits}, Hits: #{hits}, Misses: #{misses}\n"  
381 - end 398 + if n == 0
  399 + next
  400 + end
  401 + est_p = right_appearances.to_f / n.to_f
  402 + z = (est_p - 0.5).abs / Math.sqrt((0.5 * 0.5) / n.to_f)
382 403
383 - if yesterday_appearances > 5 # this test isn't worthwhile for small numbers of appearances  
384 - miss_rate = misses.to_f / yesterday_appearances.to_f  
385 - if miss_rate > 0.1  
386 - error_message += "Warning! Question #{question.id} has less than 90% of appearances taken from a pre-generated cache! Expected <#{0.1}, Actual: #{miss_rate}, total appearances yesterday: #{yesterday_appearances}\n" 404 + if z > 6
  405 + error_message += "Error: Choice ID #{c.id} seems to favor one side: Left Appearances #{left_appearances}, Right Appearances: #{right_appearances}, z = #{z}\n"
  406 + end
387 end 407 end
  408 + return error_message.blank? ? [success_message, false] : [error_message, true]
388 end 409 end
389 - return error_message.blank? ? [success_message, false] : [error_message, true]  
390 - end  
391 -  
392 - def check_object_counter_cache_values_match_actual_values(question)  
393 - error_message = ""  
394 - success_message = "All cached object values match actual values within database"  
395 - # Checks that counter_cache is working as expected  
396 - cached_prompts_size = question.prompts.size  
397 - actual_prompts_size = question.prompts.count 410 + def check_prompt_cache_hit_rate(question)
  411 + error_message = ""
  412 + success_message = "At least 90% of prompts on catchup algorithm questions were served from cache\n"
  413 + return [success_message, false] unless question.uses_catchup?
398 414
399 - if cached_prompts_size != actual_prompts_size  
400 - error_message += "Error! Question #{question.id} has an inconsistent # of prompts! cached#: #{cached_prompts_size}, actual#: #{actual_prompts_size}\n"  
401 - end 415 + misses = question.get_prompt_cache_misses(Date.yesterday).to_i
  416 + hits = question.get_prompt_cache_hits(Date.yesterday).to_i
402 417
403 - cached_votes_size = question.votes.size  
404 - actual_votes_size = question.votes.count 418 + question.expire_prompt_cache_tracking_keys(Date.yesterday)
405 419
406 - if cached_votes_size != actual_votes_size  
407 - error_message += "Error! Question #{question.id} has an inconsistent # of votes! cached#: #{cached_votes_size}, actual#: #{actual_votes_size}\n"  
408 - end 420 + yesterday_appearances = question.appearances.count(:conditions => ['date(created_at) = ?', Date.yesterday])
409 421
410 - cached_choices_size = question.choices.size  
411 - actual_choices_size = question.choices.count 422 + if misses + hits != yesterday_appearances
  423 + error_message += "Error! Question #{question.id} isn't tracking prompt cache hits and misses accurately! Expected #{yesterday_appearances}, Actual: #{misses+hits}, Hits: #{hits}, Misses: #{misses}\n"
  424 + end
412 425
413 - if cached_choices_size != actual_choices_size  
414 - error_message+= "Error! Question #{question.id} has an inconsistent # of choices! cached#: #{cached_choices_size}, actual#: #{actual_choices_size}\n" 426 + if yesterday_appearances > 5 # this test isn't worthwhile for small numbers of appearances
  427 + miss_rate = misses.to_f / yesterday_appearances.to_f
  428 + if miss_rate > 0.1
  429 + error_message += "Warning! Question #{question.id} has less than 90% of appearances taken from a pre-generated cache! Expected <#{0.1}, Actual: #{miss_rate}, total appearances yesterday: #{yesterday_appearances}\n"
  430 + end
  431 + end
  432 + return error_message.blank? ? [success_message, false] : [error_message, true]
415 end 433 end
416 434
417 - #if cached_prompts_size != question.choices.size **2 - question.choices.size  
418 - # error_message += "Error! Question #{question.id} has an incorrect number of prompts! Expected #{question.choices.size **2 - question.choices.size}, Actual: #{cached_prompts_size}\n"  
419 - #end  
420 - return error_message.blank? ? [success_message, false] : [error_message, true]  
421 - end 435 + def check_object_counter_cache_values_match_actual_values(question)
  436 + error_message = ""
  437 + success_message = "All cached object values match actual values within database"
  438 + # Checks that counter_cache is working as expected
  439 + cached_prompts_size = question.prompts.size
  440 + actual_prompts_size = question.prompts.count
422 441
423 - namespace :question do 442 + if cached_prompts_size != actual_prompts_size
  443 + error_message += "Error! Question #{question.id} has an inconsistent # of prompts! cached#: #{cached_prompts_size}, actual#: #{actual_prompts_size}\n"
  444 + end
424 445
425 - desc "Ensure that a question has: answered_appearances == votes + skips"  
426 - task :answered_appearances_equals_votes_and_skips, [:question_id] => :environment do |t, args|  
427 - a = cleanup_args(args)  
428 - questions = Question.find(a[:question_id])  
429 - questions.each do |question|  
430 - puts answered_appearances_equals_votes_and_skips(question).inspect 446 + cached_votes_size = question.votes.size
  447 + actual_votes_size = question.votes.count
  448 +
  449 + if cached_votes_size != actual_votes_size
  450 + error_message += "Error! Question #{question.id} has an inconsistent # of votes! cached#: #{cached_votes_size}, actual#: #{actual_votes_size}\n"
431 end 451 end
432 - end  
433 452
434 - end 453 + cached_choices_size = question.choices.size
  454 + actual_choices_size = question.choices.count
435 455
436 - def answered_appearances_equals_votes_and_skips(question)  
437 - error_message = ""  
438 - success_message = "All vote and skip objects have an associated appearance object"  
439 - skip_appearances_count = Appearance.count(  
440 - :conditions => ["skips.valid_record = 1 and appearances.question_id = ? AND answerable_id IS NOT NULL AND answerable_type = 'Skip'", question.id],  
441 - :joins => "LEFT JOIN skips ON (skips.id = appearances.answerable_id)")  
442 - vote_appearances_count = Appearance.count(  
443 - :conditions => ["votes.valid_record = 1 and appearances.question_id = ? AND answerable_id IS NOT NULL and answerable_type = 'Vote'", question.id],  
444 - :joins => "LEFT JOIN votes ON (votes.id = appearances.answerable_id)")  
445 - total_answered_appearances = skip_appearances_count + vote_appearances_count  
446 - total_votes = question.votes.count  
447 - total_skips = question.skips.count  
448 - if (total_answered_appearances != total_votes + total_skips)  
449 - error_message += "Question #{question.id}: answered_appearances = #{total_answered_appearances}, votes = #{total_votes}, skips = #{total_skips}" 456 + if cached_choices_size != actual_choices_size
  457 + error_message+= "Error! Question #{question.id} has an inconsistent # of choices! cached#: #{cached_choices_size}, actual#: #{actual_choices_size}\n"
  458 + end
  459 +
  460 + #if cached_prompts_size != question.choices.size **2 - question.choices.size
  461 + # error_message += "Error! Question #{question.id} has an incorrect number of prompts! Expected #{question.choices.size **2 - question.choices.size}, Actual: #{cached_prompts_size}\n"
  462 + #end
  463 + return error_message.blank? ? [success_message, false] : [error_message, true]
450 end 464 end
  465 + end
451 466
  467 + # END OF QUESTION NAMESPACE
452 468
453 - return error_message.blank? ? [success_message, false] : [error_message, true]  
454 - end  
455 469
456 def response_time_tests 470 def response_time_tests
457 error_message = "" 471 error_message = ""