Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

bug777670 - backfills slow jobs with time #743

Merged
merged 3 commits into from over 1 year ago

2 participants

Peter Bengtsson Chris Lonnen
Peter Bengtsson
Owner

r?

Added tests that attempt to prove that it works to have backfillable jobs that specify a time and what happens if the last_success key is removed.

Chris Lonnen
Owner

... cannot be merged. please update and reopen.

Chris Lonnen lonnen closed this September 26, 2012
Peter Bengtsson
Owner

nasty http://cl.ly/JlUA

BTw i prefer to leave it open. Closed to me means its done with. that no more is required or possible.

Peter Bengtsson peterbe reopened this September 27, 2012
Chris Lonnen
Owner
socorro/cron/crontabber.py:299: undefined name 'utc'
socorro/cron/crontabber.py:536:27: E251 no spaces around keyword / parameter equals
Peter Bengtsson
Owner

My pep8 on socorro was way out of date. All fixed now for pep8 1.3.3

Chris Lonnen
Owner

:shipit:

Chris Lonnen lonnen merged commit 9da90ff into from October 19, 2012
Chris Lonnen lonnen closed this October 19, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
109  socorro/cron/crontabber.py
@@ -70,13 +70,16 @@ def main(self, function=None, once=True):
70 70
             yield now
71 71
         else:
72 72
             # figure out when it was last run
73  
-            last_success = self.job_information.get('last_success',
74  
-              self.job_information.get('first_run'))
  73
+            last_success = self.job_information.get(
  74
+                'last_success',
  75
+                self.job_information.get('first_run')
  76
+            )
75 77
             if not last_success:
76 78
                 # either it has never run successfully or it was previously run
77 79
                 # before the 'first_run' key was added (legacy).
78 80
                 self.config.logger.warning(
79  
-                  'No previous last_success information available')
  81
+                    'No previous last_success information available'
  82
+                )
80 83
                 function(now)
81 84
                 yield now
82 85
             else:
@@ -264,7 +267,7 @@ def _save_to_postgres(self):
264 267
             connection.commit()
265 268
 
266 269
 
267  
-def timesince(d, now=None):  # pragma: no cover
  270
+def timesince(d, now):  # pragma: no cover
268 271
     """
269 272
     Taken from django.utils.timesince
270 273
     """
@@ -280,12 +283,12 @@ def is_aware(v):
280 283
         return v.tzinfo is not None and v.tzinfo.utcoffset(v) is not None
281 284
 
282 285
     chunks = (
283  
-      (60 * 60 * 24 * 365, lambda n: ungettext('year', 'years', n)),
284  
-      (60 * 60 * 24 * 30, lambda n: ungettext('month', 'months', n)),
285  
-      (60 * 60 * 24 * 7, lambda n: ungettext('week', 'weeks', n)),
286  
-      (60 * 60 * 24, lambda n: ungettext('day', 'days', n)),
287  
-      (60 * 60, lambda n: ungettext('hour', 'hours', n)),
288  
-      (60, lambda n: ungettext('minute', 'minutes', n))
  286
+        (60 * 60 * 24 * 365, lambda n: ungettext('year', 'years', n)),
  287
+        (60 * 60 * 24 * 30, lambda n: ungettext('month', 'months', n)),
  288
+        (60 * 60 * 24 * 7, lambda n: ungettext('week', 'weeks', n)),
  289
+        (60 * 60 * 24, lambda n: ungettext('day', 'days', n)),
  290
+        (60 * 60, lambda n: ungettext('hour', 'hours', n)),
  291
+        (60, lambda n: ungettext('minute', 'minutes', n))
289 292
     )
290 293
     # Convert datetime.date to datetime.datetime for comparison.
291 294
     if not isinstance(d, datetime.datetime):
@@ -294,7 +297,7 @@ def is_aware(v):
294 297
         now = datetime.datetime(now.year, now.month, now.day)
295 298
 
296 299
     if not now:
297  
-        now = datetime.datetime.now(utc if is_aware(d) else None)
  300
+        now = datetime.datetime.utcnow()
298 301
 
299 302
     delta = now - d
300 303
     # ignore microseconds
@@ -307,14 +310,16 @@ def is_aware(v):
307 310
         if count != 0:
308 311
             break
309 312
     s = ugettext('%(number)d %(type)s') % {
310  
-      'number': count, 'type': name(count)}
  313
+        'number': count, 'type': name(count)
  314
+    }
311 315
     if i + 1 < len(chunks):
312 316
         # Now get the second item
313 317
         seconds2, name2 = chunks[i + 1]
314 318
         count2 = (since - (seconds * count)) // seconds2
315 319
         if count2 != 0:
316 320
             s += ugettext(', %(number)d %(type)s') % {
317  
-                 'number': count2, 'type': name2(count2)}
  321
+                'number': count2, 'type': name2(count2)
  322
+            }
318 323
     return s
319 324
 
320 325
 
@@ -332,12 +337,11 @@ def _default_extra_extractor(list_element):
332 337
 
333 338
 
334 339
 def classes_in_namespaces_converter_with_compression(
335  
-      reference_namespace={},
336  
-      template_for_namespace="class-%(name)s",
337  
-      list_splitter_fn=_default_list_splitter,
338  
-      class_extractor=_default_class_extractor,
339  
-      extra_extractor=_default_extra_extractor
340  
-    ):
  340
+        reference_namespace={},
  341
+        template_for_namespace="class-%(name)s",
  342
+        list_splitter_fn=_default_list_splitter,
  343
+        class_extractor=_default_class_extractor,
  344
+        extra_extractor=_default_extra_extractor):
341 345
     """
342 346
     parameters:
343 347
         template_for_namespace - a template for the names of the namespaces
@@ -392,16 +396,20 @@ class InnerClassList(RequiredConfig):
392 396
             # for each class in the class list
393 397
             class_list = []
394 398
             for namespace_index, class_list_element in enumerate(
395  
-                                                               class_str_list):
  399
+                class_str_list
  400
+            ):
396 401
                 try:
397 402
                     a_class = class_converter(
398  
-                                           class_extractor(class_list_element))
  403
+                        class_extractor(class_list_element)
  404
+                    )
399 405
                 except AttributeError:
400 406
                     raise JobNotFoundError(class_list_element)
401 407
                 class_list.append((a_class.__name__, a_class))
402 408
                 # figure out the Namespace name
403  
-                namespace_name_dict = {'name': a_class.__name__,
404  
-                                       'index': namespace_index}
  409
+                namespace_name_dict = {
  410
+                    'name': a_class.__name__,
  411
+                    'index': namespace_index
  412
+                }
405 413
                 namespace_name = template_for_namespace % namespace_name_dict
406 414
                 subordinate_namespace_names.append(namespace_name)
407 415
                 # create the new Namespace
@@ -447,19 +455,21 @@ def get_extra_as_options(input_str):
447 455
         frequency, time_ = metadata
448 456
 
449 457
     n = Namespace()
450  
-    n.add_option('frequency',
451  
-                 doc='frequency',
452  
-                 default=frequency,
453  
-                 #from_string_converter=int
454  
-                 exclude_from_print_conf=True,
455  
-                 exclude_from_dump_conf=True
456  
-                 )
457  
-    n.add_option('time',
458  
-                 doc='time',
459  
-                 default=time_,
460  
-                 exclude_from_print_conf=True,
461  
-                 exclude_from_dump_conf=True
462  
-                 )
  458
+    n.add_option(
  459
+        'frequency',
  460
+        doc='frequency',
  461
+        default=frequency,
  462
+        #from_string_converter=int
  463
+        exclude_from_print_conf=True,
  464
+        exclude_from_dump_conf=True
  465
+    )
  466
+    n.add_option(
  467
+        'time',
  468
+        doc='time',
  469
+        default=time_,
  470
+        exclude_from_print_conf=True,
  471
+        exclude_from_dump_conf=True
  472
+    )
463 473
     return n
464 474
 
465 475
 
@@ -529,14 +539,13 @@ class CronTabber(App):
529 539
     )
530 540
 
531 541
     required_config.crontabber.add_option(
532  
-      'jobs',
533  
-      default='',
534  
-      from_string_converter=
535  
-        classes_in_namespaces_converter_with_compression(
536  
-          reference_namespace=required_config.crontabber,
537  
-          list_splitter_fn=line_splitter,
538  
-          class_extractor=pipe_splitter,
539  
-          extra_extractor=get_extra_as_options
  542
+        'jobs',
  543
+        default='',
  544
+        from_string_converter=classes_in_namespaces_converter_with_compression(
  545
+            reference_namespace=required_config.crontabber,
  546
+            list_splitter_fn=line_splitter,
  547
+            class_extractor=pipe_splitter,
  548
+            extra_extractor=get_extra_as_options
540 549
         )
541 550
     )
542 551
 
@@ -663,8 +672,10 @@ def run_one(self, description, force=False):
663 672
         # the description in this case is either the app_name or the full
664 673
         # module/class reference
665 674
         for class_name, job_class in self.config.crontabber.jobs.class_list:
666  
-            if (job_class.app_name == description or
667  
-              description == job_class.__module__ + '.' + job_class.__name__):
  675
+            if (
  676
+                job_class.app_name == description or
  677
+                description == job_class.__module__ + '.' + job_class.__name__
  678
+            ):
668 679
                 class_config = self.config.crontabber['class-%s' % class_name]
669 680
                 self._run_one(job_class, class_config, force=force)
670 681
                 return
@@ -776,9 +787,9 @@ def _log_run(self, class_, seconds, time_, last_success, now,
776 787
         if exc_type:
777 788
             tb = ''.join(traceback.format_tb(exc_tb))
778 789
             info['last_error'] = {
779  
-              'type': exc_type,
780  
-              'value': str(exc_value),
781  
-              'traceback': tb,
  790
+                'type': exc_type,
  791
+                'value': str(exc_value),
  792
+                'traceback': tb,
782 793
             }
783 794
             info['error_count'] = info.get('error_count', 0) + 1
784 795
         else:
335  socorro/unittest/cron/test_crontabber.py
@@ -56,10 +56,10 @@ def test_loading_existing_file(self):
56 56
         file1 = os.path.join(self.tempdir, 'file1.json')
57 57
 
58 58
         stuff = {
59  
-          'foo': 1,
60  
-          'more': {
61  
-            'bar': u'Bar'
62  
-          }
  59
+            'foo': 1,
  60
+            'more': {
  61
+                'bar': u'Bar'
  62
+            }
63 63
         }
64 64
         json.dump(stuff, open(file1, 'w'))
65 65
         db.load(file1)
@@ -77,8 +77,10 @@ def test_saving_new_file(self):
77 77
         db['more'] = {'bar': u'Bar'}
78 78
         db.save(file1)
79 79
         structure = json.load(open(file1))
80  
-        self.assertEqual(structure,
81  
-                         {u'foo': 1, u'more': {u'bar': u'Bar'}})
  80
+        self.assertEqual(
  81
+            structure,
  82
+            {u'foo': 1, u'more': {u'bar': u'Bar'}}
  83
+        )
82 84
 
83 85
         # check that save doesn't actually change anything
84 86
         self.assertEqual(db['foo'], 1)
@@ -134,7 +136,7 @@ def tearDown(self):
134 136
 
135 137
     def test_basic_run_job(self):
136 138
         config_manager, json_file = self._setup_config_manager(
137  
-          'socorro.unittest.cron.test_crontabber.BasicJob|7d'
  139
+            'socorro.unittest.cron.test_crontabber.BasicJob|7d'
138 140
         )
139 141
 
140 142
         def fmt(d):
@@ -190,7 +192,7 @@ def fmt(d):
190 192
     @mock.patch('socorro.cron.crontabber.utc_now')
191 193
     def test_slow_run_job(self, mocked_utc_now, time_sleep):
192 194
         config_manager, json_file = self._setup_config_manager(
193  
-          'socorro.unittest.cron.test_crontabber.SlowJob|1h'
  195
+            'socorro.unittest.cron.test_crontabber.SlowJob|1h'
194 196
         )
195 197
 
196 198
         _sleeps = []
@@ -214,7 +216,7 @@ def mock_utc_now():
214 216
             tab.run_all()
215 217
             time_after = crontabber.utc_now()
216 218
             time_taken = (time_after - time_before).seconds
217  
-            assert round(time_taken) == 1.0, time_taken
  219
+            self.assertEqual(round(time_taken), 1.0)
218 220
 
219 221
             # check that this was written to the JSON file
220 222
             # and that the next_run is going to be 1 day from now
@@ -224,12 +226,13 @@ def mock_utc_now():
224 226
             self.assertEqual(information['error_count'], 0)
225 227
             self.assertEqual(information['last_error'], {})
226 228
             self.assertTrue(information['next_run'].startswith(
227  
-                             (time_before + datetime.timedelta(hours=1))
228  
-                              .strftime('%Y-%m-%d %H:%M:%S')))
  229
+                (time_before + datetime.timedelta(hours=1))
  230
+                .strftime('%Y-%m-%d %H:%M:%S'))
  231
+            )
229 232
 
230 233
     def test_run_job_by_class_path(self):
231 234
         config_manager, json_file = self._setup_config_manager(
232  
-          'socorro.unittest.cron.test_crontabber.BasicJob|30m'
  235
+            'socorro.unittest.cron.test_crontabber.BasicJob|30m'
233 236
         )
234 237
 
235 238
         with config_manager.context() as config:
@@ -239,8 +242,8 @@ def test_run_job_by_class_path(self):
239 242
 
240 243
     def test_basic_run_all(self):
241 244
         config_manager, json_file = self._setup_config_manager(
242  
-          'socorro.unittest.cron.test_crontabber.FooJob|3d\n'
243  
-          'socorro.unittest.cron.test_crontabber.BarJob|4d'
  245
+            'socorro.unittest.cron.test_crontabber.FooJob|3d\n'
  246
+            'socorro.unittest.cron.test_crontabber.BarJob|4d'
244 247
         )
245 248
 
246 249
         with config_manager.context() as config:
@@ -276,7 +279,7 @@ def test_basic_run_all(self):
276 279
 
277 280
     def test_run_into_error_first_time(self):
278 281
         config_manager, json_file = self._setup_config_manager(
279  
-          'socorro.unittest.cron.test_crontabber.TroubleJob|7d\n'
  282
+            'socorro.unittest.cron.test_crontabber.TroubleJob|7d\n'
280 283
         )
281 284
 
282 285
         with config_manager.context() as config:
@@ -315,9 +318,9 @@ def test_run_into_error_first_time(self):
315 318
 
316 319
     def test_run_all_with_failing_dependency(self):
317 320
         config_manager, json_file = self._setup_config_manager(
318  
-          'socorro.unittest.cron.test_crontabber.TroubleJob|1d\n'
319  
-          'socorro.unittest.cron.test_crontabber.SadJob|1d\n'
320  
-          'socorro.unittest.cron.test_crontabber.BasicJob|1d'
  321
+            'socorro.unittest.cron.test_crontabber.TroubleJob|1d\n'
  322
+            'socorro.unittest.cron.test_crontabber.SadJob|1d\n'
  323
+            'socorro.unittest.cron.test_crontabber.BasicJob|1d'
321 324
         )
322 325
 
323 326
         with config_manager.context() as config:
@@ -357,7 +360,7 @@ def test_run_all_with_failing_dependency(self):
357 360
 
358 361
     def test_run_all_basic_with_failing_dependency_without_errors(self):
359 362
         config_manager, json_file = self._setup_config_manager(
360  
-          'socorro.unittest.cron.test_crontabber.BarJob|1d'
  363
+            'socorro.unittest.cron.test_crontabber.BarJob|1d'
361 364
         )
362 365
 
363 366
         # the BarJob one depends on FooJob but suppose that FooJob
@@ -372,8 +375,8 @@ def test_run_all_basic_with_failing_dependency_without_errors(self):
372 375
 
373 376
     def test_run_all_with_failing_dependency_without_errors_but_old(self):
374 377
         config_manager, json_file = self._setup_config_manager(
375  
-          'socorro.unittest.cron.test_crontabber.FooJob|1d\n'
376  
-          'socorro.unittest.cron.test_crontabber.BarJob|1d'
  378
+            'socorro.unittest.cron.test_crontabber.FooJob|1d\n'
  379
+            'socorro.unittest.cron.test_crontabber.BarJob|1d'
377 380
         )
378 381
         # the BarJob one depends on FooJob but suppose that FooJob
379 382
         # has run for but a very long time ago
@@ -394,8 +397,10 @@ def test_run_all_with_failing_dependency_without_errors_but_old(self):
394 397
             infos = [x[0][0] for x in config.logger.info.call_args_list]
395 398
             infos = [x for x in infos if x.startswith('Ran ')]
396 399
             # obvious
397  
-            self.assertEqual(infos,
398  
-              ['Ran FooJob', 'Ran BarJob', 'Ran FooJob', 'Ran BarJob'])
  400
+            self.assertEqual(
  401
+                infos,
  402
+                ['Ran FooJob', 'Ran BarJob', 'Ran FooJob', 'Ran BarJob']
  403
+            )
399 404
 
400 405
             # repeat
401 406
             self._wind_clock(json_file, days=2)
@@ -411,8 +416,8 @@ def test_run_all_with_failing_dependency_without_errors_but_old(self):
411 416
 
412 417
     def test_basic_run_job_with_hour(self):
413 418
         config_manager, json_file = self._setup_config_manager(
414  
-          'socorro.unittest.cron.test_crontabber.BasicJob|7d|03:00\n'
415  
-          'socorro.unittest.cron.test_crontabber.FooJob|1:45'
  419
+            'socorro.unittest.cron.test_crontabber.BasicJob|7d|03:00\n'
  420
+            'socorro.unittest.cron.test_crontabber.FooJob|1:45'
416 421
         )
417 422
 
418 423
         with config_manager.context() as config:
@@ -430,10 +435,10 @@ def test_basic_run_job_with_hour(self):
430 435
 
431 436
     def test_list_jobs(self):
432 437
         config_manager, json_file = self._setup_config_manager(
433  
-          'socorro.unittest.cron.test_crontabber.SadJob|5h\n'
434  
-          'socorro.unittest.cron.test_crontabber.TroubleJob|1d\n'
435  
-          'socorro.unittest.cron.test_crontabber.BasicJob|7d|03:00\n'
436  
-          'socorro.unittest.cron.test_crontabber.FooJob|2d'
  438
+            'socorro.unittest.cron.test_crontabber.SadJob|5h\n'
  439
+            'socorro.unittest.cron.test_crontabber.TroubleJob|1d\n'
  440
+            'socorro.unittest.cron.test_crontabber.BasicJob|7d|03:00\n'
  441
+            'socorro.unittest.cron.test_crontabber.FooJob|2d'
437 442
         )
438 443
 
439 444
         with config_manager.context() as config:
@@ -447,11 +452,15 @@ def test_list_jobs(self):
447 452
                 sys.stdout = old_stdout
448 453
             output = new_stdout.getvalue()
449 454
             self.assertEqual(output.count('Class:'), 4)
450  
-            self.assertEqual(4,
451  
-              len(re.findall('App name:\s+(trouble|basic-job|foo|sad)',
452  
-                             output, re.I)))
453  
-            self.assertEqual(4,
454  
-              len(re.findall('No previous run info', output, re.I)))
  455
+            self.assertEqual(
  456
+                4,
  457
+                len(re.findall('App name:\s+(trouble|basic-job|foo|sad)',
  458
+                               output, re.I))
  459
+            )
  460
+            self.assertEqual(
  461
+                4,
  462
+                len(re.findall('No previous run info', output, re.I))
  463
+            )
455 464
 
456 465
             tab.run_all()
457 466
             assert 'sad' not in tab.database
@@ -467,8 +476,10 @@ def test_list_jobs(self):
467 476
                 sys.stdout = old_stdout
468 477
             output = new_stdout.getvalue()
469 478
             # sad job won't be run since its depdendent keeps failing
470  
-            self.assertEqual(1,
471  
-              len(re.findall('No previous run info', output, re.I)))
  479
+            self.assertEqual(
  480
+                1,
  481
+                len(re.findall('No previous run info', output, re.I))
  482
+            )
472 483
 
473 484
             # split them up so that we can investigate each block of output
474 485
             outputs = {}
@@ -489,8 +500,8 @@ def test_list_jobs(self):
489 500
 
490 501
     def test_configtest_ok(self):
491 502
         config_manager, json_file = self._setup_config_manager(
492  
-          'socorro.unittest.cron.test_crontabber.FooJob|3d\n'
493  
-          'socorro.unittest.cron.test_crontabber.BarJob|4d'
  503
+            'socorro.unittest.cron.test_crontabber.FooJob|3d\n'
  504
+            'socorro.unittest.cron.test_crontabber.BarJob|4d'
494 505
         )
495 506
 
496 507
         with config_manager.context() as config:
@@ -525,7 +536,7 @@ def test_configtest_definition_error(self):
525 536
 
526 537
     def test_configtest_bad_frequency(self):
527 538
         config_manager, json_file = self._setup_config_manager(
528  
-          'socorro.unittest.cron.test_crontabber.FooJob|3e'
  539
+            'socorro.unittest.cron.test_crontabber.FooJob|3e'
529 540
         )
530 541
 
531 542
         with config_manager.context() as config:
@@ -545,8 +556,8 @@ def test_configtest_bad_frequency(self):
545 556
 
546 557
     def test_configtest_bad_time(self):
547 558
         config_manager, json_file = self._setup_config_manager(
548  
-          'socorro.unittest.cron.test_crontabber.FooJob|24:59\n'
549  
-          'socorro.unittest.cron.test_crontabber.BasicJob|23:60'
  559
+            'socorro.unittest.cron.test_crontabber.FooJob|24:59\n'
  560
+            'socorro.unittest.cron.test_crontabber.BasicJob|23:60'
550 561
         )
551 562
 
552 563
         with config_manager.context() as config:
@@ -566,7 +577,7 @@ def test_configtest_bad_time(self):
566 577
 
567 578
     def test_configtest_bad_time_invariance(self):
568 579
         config_manager, json_file = self._setup_config_manager(
569  
-          'socorro.unittest.cron.test_crontabber.FooJob|3h|23:59'
  580
+            'socorro.unittest.cron.test_crontabber.FooJob|3h|23:59'
570 581
         )
571 582
 
572 583
         with config_manager.context() as config:
@@ -586,7 +597,7 @@ def test_configtest_bad_time_invariance(self):
586 597
 
587 598
     def test_execute_postgres_based_job(self):
588 599
         config_manager, json_file = self._setup_config_manager(
589  
-          'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
  600
+            'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
590 601
         )
591 602
 
592 603
         with config_manager.context() as config:
@@ -595,17 +606,17 @@ def test_execute_postgres_based_job(self):
595 606
             config.logger.info.assert_called_with('Ran PostgresSampleJob')
596 607
 
597 608
             self.psycopg2().cursor().execute.assert_any_call(
598  
-              'INSERT INTO test_cron_victim (time) VALUES (now())'
  609
+                'INSERT INTO test_cron_victim (time) VALUES (now())'
599 610
             )
600 611
             self.psycopg2().cursor().execute.assert_any_call(
601  
-              'COMMIT'
  612
+                'COMMIT'
602 613
             )
603 614
             self.psycopg2().close.assert_called_with()
604 615
 
605 616
     def test_execute_postgres_transaction_managed_job(self):
606 617
         config_manager, json_file = self._setup_config_manager(
607  
-          'socorro.unittest.cron.test_crontabber.'
608  
-          'PostgresTransactionSampleJob|1d'
  618
+            'socorro.unittest.cron.test_crontabber.'
  619
+            'PostgresTransactionSampleJob|1d'
609 620
         )
610 621
 
611 622
         with config_manager.context() as config:
@@ -619,7 +630,7 @@ def test_execute_postgres_transaction_managed_job(self):
619 630
 
620 631
     def test_execute_failing_postgres_based_job(self):
621 632
         config_manager, json_file = self._setup_config_manager(
622  
-          'socorro.unittest.cron.test_crontabber.BrokenPostgresSampleJob|1d'
  633
+            'socorro.unittest.cron.test_crontabber.BrokenPostgresSampleJob|1d'
623 634
         )
624 635
 
625 636
         with config_manager.context() as config:
@@ -632,12 +643,15 @@ def test_execute_failing_postgres_based_job(self):
632 643
             self.assertTrue(self.psycopg2.called)
633 644
             self.psycopg2().close.assert_called_with()
634 645
             self.assertTrue(tab.database['broken-pg-job']['last_error'])
635  
-            self.assertTrue('ProgrammingError' in
636  
-                      tab.database['broken-pg-job']['last_error']['traceback'])
  646
+            self.assertTrue(
  647
+                'ProgrammingError' in
  648
+                tab.database['broken-pg-job']['last_error']['traceback']
  649
+            )
637 650
 
638 651
     def test_own_required_config_job(self):
639 652
         config_manager, json_file = self._setup_config_manager(
640  
-         'socorro.unittest.cron.test_crontabber.OwnRequiredConfigSampleJob|1d'
  653
+            'socorro.unittest.cron.test_crontabber'
  654
+            '.OwnRequiredConfigSampleJob|1d'
641 655
         )
642 656
 
643 657
         with config_manager.context() as config:
@@ -646,17 +660,18 @@ def test_own_required_config_job(self):
646 660
             infos = [x[0][0] for x in config.logger.info.call_args_list]
647 661
             infos = [x for x in infos if x.startswith('Ran ')]
648 662
             self.assertTrue(
649  
-              'Ran OwnRequiredConfigSampleJob(%r)' % 'bugz.mozilla.org'
650  
-              in infos
  663
+                'Ran OwnRequiredConfigSampleJob(%r)' % 'bugz.mozilla.org'
  664
+                in infos
651 665
             )
652 666
 
653 667
     def test_own_required_config_job_overriding_config(self):
654 668
         config_manager, json_file = self._setup_config_manager(
655  
-         'socorro.unittest.cron.test_crontabber.OwnRequiredConfigSampleJob|1d',
656  
-          extra_value_source={
657  
-            'crontabber.class-OwnRequiredConfigSampleJob.bugsy_url':
  669
+            'socorro.unittest.cron.test_crontabber'
  670
+            '.OwnRequiredConfigSampleJob|1d',
  671
+            extra_value_source={
  672
+                'crontabber.class-OwnRequiredConfigSampleJob.bugsy_url':
658 673
                 'bugs.peterbe.com'
659  
-          }
  674
+            }
660 675
         )
661 676
 
662 677
         with config_manager.context() as config:
@@ -665,13 +680,13 @@ def test_own_required_config_job_overriding_config(self):
665 680
             infos = [x[0][0] for x in config.logger.info.call_args_list]
666 681
             infos = [x for x in infos if x.startswith('Ran ')]
667 682
             self.assertTrue(
668  
-              'Ran OwnRequiredConfigSampleJob(%r)' % 'bugs.peterbe.com'
669  
-              in infos
  683
+                'Ran OwnRequiredConfigSampleJob(%r)' % 'bugs.peterbe.com'
  684
+                in infos
670 685
             )
671 686
 
672 687
     def test_automatic_backfill_basic_job(self):
673 688
         config_manager, json_file = self._setup_config_manager(
674  
-         'socorro.unittest.cron.test_crontabber.FooBackfillJob|1d'
  689
+            'socorro.unittest.cron.test_crontabber.FooBackfillJob|1d'
675 690
         )
676 691
 
677 692
         def fmt(d):
@@ -697,10 +712,12 @@ def fmt(d):
697 712
 
698 713
             # now, pretend the last 2 days have failed
699 714
             interval = datetime.timedelta(days=2)
700  
-            tab.database['foo-backfill']['first_run'] = \
701  
-              tab.database['foo-backfill']['first_run'] - interval
702  
-            tab.database['foo-backfill']['last_success'] = \
703  
-              tab.database['foo-backfill']['last_success'] - interval
  715
+            tab.database['foo-backfill']['first_run'] = (
  716
+                tab.database['foo-backfill']['first_run'] - interval
  717
+            )
  718
+            tab.database['foo-backfill']['last_success'] = (
  719
+                tab.database['foo-backfill']['last_success'] - interval
  720
+            )
704 721
             tab.database.save(json_file)
705 722
 
706 723
             self._wind_clock(json_file, days=1)
@@ -742,7 +759,8 @@ def test_backfilling_failling_midway(self):
742 759
         """
743 760
 
744 761
         config_manager, json_file = self._setup_config_manager(
745  
-         'socorro.unittest.cron.test_crontabber.CertainDayHaterBackfillJob|1d'
  762
+            'socorro.unittest.cron.test_crontabber'
  763
+            '.CertainDayHaterBackfillJob|1d'
746 764
         )
747 765
         with config_manager.context() as config:
748 766
             tab = crontabber.CronTabber(config)
@@ -752,17 +770,20 @@ def test_backfilling_failling_midway(self):
752 770
 
753 771
             # now, pretend the last 2 days have failed
754 772
             interval = datetime.timedelta(days=2)
755  
-            tab.database[app_name]['first_run'] = \
756  
-              tab.database[app_name]['first_run'] - interval
757  
-            tab.database[app_name]['last_success'] = \
758  
-              tab.database[app_name]['last_success'] - interval
  773
+            tab.database[app_name]['first_run'] = (
  774
+                tab.database[app_name]['first_run'] - interval
  775
+            )
  776
+            tab.database[app_name]['last_success'] = (
  777
+                tab.database[app_name]['last_success'] - interval
  778
+            )
759 779
             tab.database.save(json_file)
760 780
 
761 781
             self._wind_clock(json_file, days=1)
762 782
             tab._database = None
763 783
 
764  
-            CertainDayHaterBackfillJob.fail_on = \
765  
-              tab.database[app_name]['first_run'] + interval
  784
+            CertainDayHaterBackfillJob.fail_on = (
  785
+                tab.database[app_name]['first_run'] + interval
  786
+            )
766 787
 
767 788
             first_last_success = tab.database[app_name]['last_success']
768 789
             tab.run_all()
@@ -773,7 +794,7 @@ def test_backfilling_failling_midway(self):
773 794
 
774 795
     def test_backfilling_postgres_based_job(self):
775 796
         config_manager, json_file = self._setup_config_manager(
776  
-         'socorro.unittest.cron.test_crontabber.PGBackfillJob|1d'
  797
+            'socorro.unittest.cron.test_crontabber.PGBackfillJob|1d'
777 798
         )
778 799
 
779 800
         def fmt(d):
@@ -801,10 +822,12 @@ def fmt(d):
801 822
 
802 823
             # now, pretend the last 2 days have failed
803 824
             interval = datetime.timedelta(days=2)
804  
-            tab.database['pg-backfill']['first_run'] = \
805  
-              tab.database['pg-backfill']['first_run'] - interval
806  
-            tab.database['pg-backfill']['last_success'] = \
807  
-              tab.database['pg-backfill']['last_success'] - interval
  825
+            tab.database['pg-backfill']['first_run'] = (
  826
+                tab.database['pg-backfill']['first_run'] - interval
  827
+            )
  828
+            tab.database['pg-backfill']['last_success'] = (
  829
+                tab.database['pg-backfill']['last_success'] - interval
  830
+            )
808 831
             tab.database.save(json_file)
809 832
 
810 833
             self._wind_clock(json_file, days=1)
@@ -831,8 +854,8 @@ def test_run_with_excess_whitespace(self):
831 854
         # this test asserts a found bug where excess newlines
832 855
         # caused configuration exceptions
833 856
         config_manager, json_file = self._setup_config_manager(
834  
-          '\n \n'
835  
-          ' socorro.unittest.cron.test_crontabber.BasicJob|7d\n\t  \n'
  857
+            '\n \n'
  858
+            ' socorro.unittest.cron.test_crontabber.BasicJob|7d\n\t  \n'
836 859
         )
837 860
 
838 861
         with config_manager.context() as config:
@@ -880,7 +903,7 @@ def test_backfilling_with_configured_time_slow_job(self,
880 903
         think 24 hours hasn't gone since the last time. Phew!
881 904
         """
882 905
         config_manager, json_file = self._setup_config_manager(
883  
-          'socorro.unittest.cron.test_crontabber.SlowBackfillJob|1d|18:00'
  906
+            'socorro.unittest.cron.test_crontabber.SlowBackfillJob|1d|18:00'
884 907
         )
885 908
         SlowBackfillJob.times_used = []
886 909
 
@@ -942,6 +965,111 @@ def mock_utc_now():
942 965
             self.assertTrue('18:01:01' in information['last_run'])
943 966
             self.assertTrue('18:00:00' in information['last_success'])
944 967
 
  968
+    @mock.patch('socorro.cron.crontabber.utc_now')
  969
+    @mock.patch('time.sleep')
  970
+    def test_slow_backfilled_timed_daily_job(self, time_sleep, mocked_utc_now):
  971
+        config_manager, json_file = self._setup_config_manager(
  972
+            'socorro.unittest.cron.test_crontabber.SlowBackfillJob|1d|10:00'
  973
+        )
  974
+
  975
+        SlowBackfillJob.times_used = []
  976
+
  977
+        _extra_time = []
  978
+
  979
+        def mocked_sleep(seconds):
  980
+            _extra_time.append(datetime.timedelta(seconds=seconds))
  981
+
  982
+        def mock_utc_now():
  983
+            n = utc_now()
  984
+            for e in _extra_time:
  985
+                n += e
  986
+            return n
  987
+
  988
+        time_sleep.side_effect = mocked_sleep
  989
+        mocked_utc_now.side_effect = mock_utc_now
  990
+
  991
+        with config_manager.context() as config:
  992
+            tab = crontabber.CronTabber(config)
  993
+            time_before = crontabber.utc_now()
  994
+            tab.run_all()
  995
+            assert len(SlowBackfillJob.times_used) == 1
  996
+            time_after = crontabber.utc_now()
  997
+            # double-checking
  998
+            assert (time_after - time_before).seconds == 1
  999
+
  1000
+            structure = json.load(open(json_file))
  1001
+            information = structure['slow-backfill']
  1002
+            self.assertTrue(information['last_success'])
  1003
+            self.assertTrue(not information['last_error'])
  1004
+            # easy
  1005
+            self.assertTrue('10:00:00' in information['next_run'])
  1006
+            self.assertEqual(information['first_run'], information['last_run'])
  1007
+
  1008
+            # pretend one day passes
  1009
+            _extra_time.append(datetime.timedelta(days=1))
  1010
+            time_later = crontabber.utc_now()
  1011
+            assert (time_later - time_after).days == 1
  1012
+            assert (time_later - time_after).seconds == 0
  1013
+            assert (time_later - time_before).days == 1
  1014
+            assert (time_later - time_before).seconds == 1
  1015
+
  1016
+            tab.run_all()
  1017
+            self.assertEqual(len(SlowBackfillJob.times_used), 2)
  1018
+            structure = json.load(open(json_file))
  1019
+            information = structure['slow-backfill']
  1020
+
  1021
+            # another day passes
  1022
+            _extra_time.append(datetime.timedelta(days=1))
  1023
+            # also, simulate that it starts a second earlier this time
  1024
+            _extra_time.append(-datetime.timedelta(seconds=1))
  1025
+            tab.run_all()
  1026
+            assert len(SlowBackfillJob.times_used) == 3
  1027
+            structure = json.load(open(json_file))
  1028
+            information = structure['slow-backfill']
  1029
+
  1030
+    @mock.patch('socorro.cron.crontabber.utc_now')
  1031
+    @mock.patch('time.sleep')
  1032
+    def test_slow_backfilled_timed_daily_job_first_failure(self,
  1033
+                                                           time_sleep,
  1034
+                                                           mocked_utc_now):
  1035
+        config_manager, json_file = self._setup_config_manager(
  1036
+            'socorro.unittest.cron.test_crontabber.SlowBackfillJob|1d|10:00'
  1037
+        )
  1038
+
  1039
+        SlowBackfillJob.times_used = []
  1040
+
  1041
+        _extra_time = []
  1042
+
  1043
+        def mocked_sleep(seconds):
  1044
+            _extra_time.append(datetime.timedelta(seconds=seconds))
  1045
+
  1046
+        def mock_utc_now():
  1047
+            n = utc_now()
  1048
+            for e in _extra_time:
  1049
+                n += e
  1050
+            return n
  1051
+
  1052
+        time_sleep.side_effect = mocked_sleep
  1053
+        mocked_utc_now.side_effect = mock_utc_now
  1054
+
  1055
+        with config_manager.context() as config:
  1056
+            tab = crontabber.CronTabber(config)
  1057
+            tab.run_all()
  1058
+            self.assertEqual(len(SlowBackfillJob.times_used), 1)
  1059
+
  1060
+            db = crontabber.JSONJobDatabase()
  1061
+            db.load(json_file)
  1062
+            del db['slow-backfill']['last_success']
  1063
+            db.save(json_file)
  1064
+
  1065
+        _extra_time.append(datetime.timedelta(days=1))
  1066
+        _extra_time.append(-datetime.timedelta(seconds=1))
  1067
+
  1068
+        with config_manager.context() as config:
  1069
+            tab = crontabber.CronTabber(config)
  1070
+            tab.run_all()
  1071
+            self.assertEqual(len(SlowBackfillJob.times_used), 2)
  1072
+
945 1073
 
946 1074
 #==============================================================================
947 1075
 @attr(integration='postgres')  # for nosetests
@@ -985,7 +1113,7 @@ def tearDown(self):
985 1113
 
986 1114
     def test_postgres_job(self):
987 1115
         config_manager, json_file = self._setup_config_manager(
988  
-          'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
  1116
+            'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
989 1117
         )
990 1118
 
991 1119
         cur = self.conn.cursor()
@@ -1015,14 +1143,18 @@ def test_postgres_job(self):
1015 1143
 
1016 1144
     def test_postgres_job_with_state_loaded_from_postgres_first(self):
1017 1145
         config_manager, json_file = self._setup_config_manager(
1018  
-          'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
  1146
+            'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
1019 1147
         )
1020 1148
 
1021 1149
         cur = self.conn.cursor()
1022 1150
         tomorrow = utc_now() + datetime.timedelta(days=1)
1023  
-        information = {'sample-pg-job': {
1024  
-          'next_run': tomorrow.strftime(crontabber.JSONJobDatabase._date_fmt),
1025  
-        }}
  1151
+        information = {
  1152
+            'sample-pg-job': {
  1153
+                'next_run': tomorrow.strftime(
  1154
+                    crontabber.JSONJobDatabase._date_fmt
  1155
+                ),
  1156
+            }
  1157
+        }
1026 1158
         information_json = json.dumps(information)
1027 1159
         cur.execute('update crontabber_state set state=%s',
1028 1160
                     (information_json,))
@@ -1038,8 +1170,10 @@ def test_postgres_job_with_state_loaded_from_postgres_first(self):
1038 1170
 
1039 1171
     def test_postgres_job_with_broken(self):
1040 1172
         config_manager, json_file = self._setup_config_manager(
1041  
-          'socorro.unittest.cron.test_crontabber.BrokenPostgresSampleJob|1d\n'
1042  
-          'socorro.unittest.cron.test_crontabber.PostgresSampleJob|1d'
  1173
+            'socorro.unittest.cron.test_crontabber'
  1174
+            '.BrokenPostgresSampleJob|1d\n'
  1175
+            'socorro.unittest.cron.test_crontabber'
  1176
+            '.PostgresSampleJob|1d'
1043 1177
         )
1044 1178
 
1045 1179
         cur = self.conn.cursor()
@@ -1073,7 +1207,8 @@ def test_postgres_job_with_broken(self):
1073 1207
 
1074 1208
     def test_postgres_job_with_backfill_basic(self):
1075 1209
         config_manager, json_file = self._setup_config_manager(
1076  
-          'socorro.unittest.cron.test_crontabber.PostgresBackfillSampleJob|1d'
  1210
+            'socorro.unittest.cron.test_crontabber'
  1211
+            '.PostgresBackfillSampleJob|1d'
1077 1212
         )
1078 1213
 
1079 1214
         cur = self.conn.cursor()
@@ -1093,7 +1228,8 @@ def test_postgres_job_with_backfill_basic(self):
1093 1228
 
1094 1229
     def test_postgres_job_with_backfill_3_days_back(self):
1095 1230
         config_manager, json_file = self._setup_config_manager(
1096  
-          'socorro.unittest.cron.test_crontabber.PostgresBackfillSampleJob|1d'
  1231
+            'socorro.unittest.cron.test_crontabber'
  1232
+            '.PostgresBackfillSampleJob|1d'
1097 1233
         )
1098 1234
 
1099 1235
         def fmt(d):
@@ -1134,10 +1270,12 @@ def fmt(d):
1134 1270
 
1135 1271
             # now, pretend the last 2 days have failed
1136 1272
             interval = datetime.timedelta(days=2)
1137  
-            tab.database[app_name]['first_run'] = \
1138  
-              tab.database[app_name]['first_run'] - interval
1139  
-            tab.database[app_name]['last_success'] = \
1140  
-              tab.database[app_name]['last_success'] - interval
  1273
+            tab.database[app_name]['first_run'] = (
  1274
+                tab.database[app_name]['first_run'] - interval
  1275
+            )
  1276
+            tab.database[app_name]['last_success'] = (
  1277
+                tab.database[app_name]['last_success'] - interval
  1278
+            )
1141 1279
             tab.database.save(json_file)
1142 1280
 
1143 1281
             self._wind_clock(json_file, days=1)
@@ -1208,6 +1346,7 @@ def run(self):
1208 1346
         time.sleep(1)  # time.sleep() is a mock function by the way
1209 1347
         super(SlowJob, self).run()
1210 1348
 
  1349
+
1211 1350
 class TroubleJob(_Job):
1212 1351
     app_name = 'trouble'
1213 1352
 
@@ -1261,8 +1400,8 @@ class OwnRequiredConfigSampleJob(_Job):
1261 1400
     )
1262 1401
 
1263 1402
     def run(self):
1264  
-        self.config.logger.info("Ran %s(%r)" %
1265  
-          (self.__class__.__name__, self.config.bugsy_url)
  1403
+        self.config.logger.info(
  1404
+            "Ran %s(%r)" % (self.__class__.__name__, self.config.bugsy_url)
1266 1405
         )
1267 1406
 
1268 1407
 
@@ -1272,7 +1411,7 @@ def run(self, date):
1272 1411
         assert isinstance(date, datetime.datetime)
1273 1412
         assert self.app_name
1274 1413
         self.config.logger.info(
1275  
-          "Ran %s(%s, %s)" % (self.__class__.__name__, date, id(date))
  1414
+            "Ran %s(%s, %s)" % (self.__class__.__name__, date, id(date))
1276 1415
         )
1277 1416
 
1278 1417
 
@@ -1287,7 +1426,7 @@ class CertainDayHaterBackfillJob(_BackfillJob):
1287 1426
 
1288 1427
     def run(self, date):
1289 1428
         if (self.fail_on
1290  
-             and date.strftime('%m%d') == self.fail_on.strftime('%m%d')):
  1429
+            and date.strftime('%m%d') == self.fail_on.strftime('%m%d')):
1291 1430
             raise Exception("bad date!")
1292 1431
 
1293 1432
 
@@ -1317,7 +1456,7 @@ def run(self, connection, date):
1317 1456
         # And since the winding back in the test is "unnatural" the numbers
1318 1457
         # in the dates are actually the same but the instances are different
1319 1458
         self.config.logger.info(
1320  
-          "Ran %s(%s, %r)" % (self.__class__.__name__, date, id(date))
  1459
+            "Ran %s(%s, %r)" % (self.__class__.__name__, date, id(date))
1321 1460
         )
1322 1461
 
1323 1462
 
@@ -1331,5 +1470,5 @@ def run(self, connection, date):
1331 1470
         # need this because this is not a TransactionManaged subclass
1332 1471
         cursor.execute('COMMIT')
1333 1472
         self.config.logger.info(
1334  
-          "Ran %s(%s, %r)" % (self.__class__.__name__, date, id(date))
  1473
+            "Ran %s(%s, %r)" % (self.__class__.__name__, date, id(date))
1335 1474
         )
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.