def test_diff_for_humans_accuracy(): now = pendulum.now("utc") with pendulum.test(now.add(microseconds=200)): assert "1 year" == now.add(years=1).diff_for_humans(absolute=True) assert "11 months" == now.add(months=11).diff_for_humans(absolute=True) assert "4 weeks" == now.add(days=27).diff_for_humans(absolute=True) assert "1 year" == now.add(years=1, months=3).diff_for_humans(absolute=True) assert "2 years" == now.add(years=1, months=8).diff_for_humans(absolute=True) # DST now = pendulum.datetime(2017, 3, 7, tz="America/Toronto") with pendulum.test(now): assert "6 days" == now.add(days=6).diff_for_humans(absolute=True)
def test_diff_for_humans_absolute_hours(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(hours=3), True) == "3 hours" now = pendulum.now().time() assert now.diff_for_humans(now.add(hours=3), True) == "3 hours"
def test_diff_for_humans_other_and_month(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "4 weeks before" == pendulum.now().diff_for_humans( pendulum.now().add(weeks=4) ) assert "1 month before" == pendulum.now().diff_for_humans( pendulum.now().add(months=1) )
def test_diff_for_humans_other_and_future_month(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "4 weeks after" == pendulum.now().diff_for_humans( pendulum.now().subtract(weeks=4) ) assert "1 month after" == pendulum.now().diff_for_humans( pendulum.now().subtract(months=1) )
def test_diff_for_humans_absolute_minutes(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "30 minutes" == pendulum.now().diff_for_humans( pendulum.now().subtract(minutes=30), True ) assert "30 minutes" == pendulum.now().diff_for_humans( pendulum.now().add(minutes=30), True )
def test_diff_for_humans_absolute_seconds(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "59 seconds" == pendulum.now().diff_for_humans( pendulum.now().subtract(seconds=59), True ) assert "59 seconds" == pendulum.now().diff_for_humans( pendulum.now().add(seconds=59), True )
def test_diff_for_humans_absolute_hours(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "3 hours" == pendulum.now().diff_for_humans( pendulum.now().subtract(hours=3), True ) assert "3 hours" == pendulum.now().diff_for_humans( pendulum.now().add(hours=3), True )
def test_diff_for_humans_absolute_months(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "2 months" == pendulum.now().diff_for_humans( pendulum.now().subtract(months=2), True ) assert "2 months" == pendulum.now().diff_for_humans( pendulum.now().add(months=2), True )
def test_diff_for_humans_absolute_years(): with pendulum.test(pendulum.datetime(2012, 1, 1, 1, 2, 3)): assert "1 year" == pendulum.now().diff_for_humans( pendulum.now().subtract(years=1), True ) assert "1 year" == pendulum.now().diff_for_humans( pendulum.now().add(years=1), True )
def test_diff_for_humans_absolute_minutes(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(minutes=30), True) == "30 minutes" now = pendulum.now().time() assert now.diff_for_humans(now.add(minutes=30), True) == "30 minutes"
def test_diff_for_humans_absolute_seconds(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(seconds=59), True) == "59 seconds" now = pendulum.now().time() assert now.diff_for_humans(now.add(seconds=59), True) == "59 seconds"
def test_from_format(text, fmt, expected, now): if now is None: now = pendulum.datetime(2015, 11, 12) else: now = pendulum.parse(now) # Python 2.7 loses precision for x timestamps # so we don't test if fmt == "x" and PY2: return with pendulum.test(now): assert pendulum.from_format(text, fmt).isoformat() == expected
def test_is_birthday(): with pendulum.test(pendulum.now()): d = pendulum.now() a_birthday = d.subtract(years=1) assert a_birthday.is_birthday() not_a_birthday = d.subtract(days=1) assert not not_a_birthday.is_birthday() also_not_a_birthday = d.add(days=2) assert not also_not_a_birthday.is_birthday() d1 = pendulum.datetime(1987, 4, 23) d2 = pendulum.datetime(2014, 9, 26) d3 = pendulum.datetime(2014, 4, 23) assert not d2.is_birthday(d1) assert d3.is_birthday(d1)
def test_diff_for_humans_other_and_future_minute(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(minutes=1)) == "1 minute after"
def test_diff_for_humans_now_and_minute(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.subtract(minutes=1).diff_for_humans() == "1 minute ago"
def test_from_format_error(text, fmt, locale): now = pendulum.datetime(2018, 2, 2) with pendulum.test(now): with pytest.raises(ValueError): pendulum.from_format(text, fmt, locale=locale)
def test_failure_recovery_before_run_created(external_repo_context, crash_location, crash_signal, capfd): # Verify that if the scheduler crashes or is interrupted before a run is created, # it will create exactly one tick/run when it is re-launched with instance_with_schedules(external_repo_context) as ( instance, _grpc_server_registry, external_repo, ): initial_datetime = to_timezone( create_pendulum_time(year=2019, month=2, day=27, hour=0, minute=0, second=0, tz="UTC"), "US/Central", ) frozen_datetime = initial_datetime.add() external_schedule = external_repo.get_external_schedule( "simple_schedule") with pendulum.test(frozen_datetime): instance.start_schedule_and_update_storage_state(external_schedule) debug_crash_flags = { external_schedule.name: { crash_location: crash_signal } } scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, debug_crash_flags], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode != 0 captured = capfd.readouterr() assert ( captured.out.replace("\r\n", "\n") == """2019-02-26 18:00:00 - SchedulerDaemon - INFO - Checking for new runs for the following schedules: simple_schedule 2019-02-26 18:00:00 - SchedulerDaemon - INFO - Evaluating schedule `simple_schedule` at 2019-02-27 00:00:00+0000 """) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 assert ticks[0].status == JobTickStatus.STARTED assert instance.get_runs_count() == 0 frozen_datetime = frozen_datetime.add(minutes=5) with pendulum.test(frozen_datetime): scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, None], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode == 0 assert instance.get_runs_count() == 1 wait_for_all_runs_to_start(instance) validate_run_started( instance.get_runs()[0], execution_time=initial_datetime, partition_time=create_pendulum_time(2019, 2, 26), ) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_schedule, initial_datetime, JobTickStatus.SUCCESS, [instance.get_runs()[0].run_id], ) captured = capfd.readouterr() assert ( captured.out.replace("\r\n", "\n") == """2019-02-26 18:05:00 - SchedulerDaemon - INFO - Checking for new runs for the following schedules: simple_schedule 2019-02-26 18:05:00 - SchedulerDaemon - INFO - Evaluating schedule `simple_schedule` at 2019-02-27 00:00:00+0000 2019-02-26 18:05:00 - SchedulerDaemon - INFO - Resuming previously interrupted schedule execution 2019-02-26 18:05:00 - SchedulerDaemon - INFO - Completed scheduled launch of run {run_id} for simple_schedule """.format(run_id=instance.get_runs()[0].run_id))
def test_failure_recovery_between_multi_runs(external_repo_context, crash_location, crash_signal): with instance_with_schedules(external_repo_context) as ( instance, _grpc_server_registry, external_repo, ): initial_datetime = create_pendulum_time(year=2019, month=2, day=28, hour=0, minute=0, second=0) frozen_datetime = initial_datetime.add() external_schedule = external_repo.get_external_schedule( "multi_run_schedule") with pendulum.test(frozen_datetime): instance.start_schedule_and_update_storage_state(external_schedule) debug_crash_flags = { external_schedule.name: { crash_location: crash_signal } } scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, debug_crash_flags], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode != 0 wait_for_all_runs_to_start(instance) assert instance.get_runs_count() == 1 validate_run_started(instance.get_runs()[0], initial_datetime) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 frozen_datetime = frozen_datetime.add(minutes=1) with pendulum.test(frozen_datetime): scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, None], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode == 0 assert instance.get_runs_count() == 2 validate_run_started(instance.get_runs()[0], initial_datetime) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_schedule, initial_datetime, JobTickStatus.SUCCESS, [run.run_id for run in instance.get_runs()], )
def test_simple_sensor(external_repo_context, capfd): freeze_datetime = to_timezone( create_pendulum_time(year=2019, month=2, day=27, hour=23, minute=59, second=59, tz="UTC"), "US/Central", ) with instance_with_sensors(external_repo_context) as ( instance, workspace, external_repo, ): with pendulum.test(freeze_datetime): external_sensor = external_repo.get_external_sensor("simple_sensor") instance.add_job_state( JobState(external_sensor.get_external_origin(), JobType.SENSOR, JobStatus.RUNNING) ) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 0 evaluate_sensors(instance, workspace) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SKIPPED, ) captured = capfd.readouterr() assert ( captured.out == """2019-02-27 17:59:59 - SensorDaemon - INFO - Checking for new runs for sensor: simple_sensor 2019-02-27 17:59:59 - SensorDaemon - INFO - Sensor returned false for simple_sensor, skipping """ ) freeze_datetime = freeze_datetime.add(seconds=30) with pendulum.test(freeze_datetime): evaluate_sensors(instance, workspace) wait_for_all_runs_to_start(instance) assert instance.get_runs_count() == 1 run = instance.get_runs()[0] validate_run_started(run) ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 2 expected_datetime = create_pendulum_time( year=2019, month=2, day=28, hour=0, minute=0, second=29 ) validate_tick( ticks[0], external_sensor, expected_datetime, JobTickStatus.SUCCESS, [run.run_id], ) captured = capfd.readouterr() assert ( captured.out == """2019-02-27 18:00:29 - SensorDaemon - INFO - Checking for new runs for sensor: simple_sensor 2019-02-27 18:00:29 - SensorDaemon - INFO - Launching run for simple_sensor 2019-02-27 18:00:29 - SensorDaemon - INFO - Completed launch of run {run_id} for simple_sensor """.format( run_id=run.run_id ) )
def test_diff_for_humans_now_and_future_hours(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.add(hours=2).diff_for_humans() == "in 2 hours"
def test_diff_for_humans_other_and_seconds(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans( now.add(seconds=2)) == "a few seconds before"
def test_diff_for_humans_now_and_future_minute(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.add(minutes=1).diff_for_humans() == "in 1 minute"
def test_diff_for_humans_now_and_nearly_future_hour(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.add(minutes=59).diff_for_humans() == "in 59 minutes"
def test_diff_for_humans_now_and_future_seconds(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.add(seconds=2).diff_for_humans() == "in a few seconds"
def test_diff_for_humans_now_and_hours(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.subtract(hours=2).diff_for_humans() == "2 hours ago"
def test_diff_for_humans_now_and_nearly_hour(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.subtract(minutes=59).diff_for_humans() == "59 minutes ago"
def test_diff_for_humans_other_and_future_hours(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(hours=2)) == "2 hours after"
def test_launch_once(external_repo_context, capfd): freeze_datetime = to_timezone( create_pendulum_time( year=2019, month=2, day=27, hour=23, minute=59, second=59, tz="UTC", ), "US/Central", ) with instance_with_sensors(external_repo_context) as ( instance, workspace, external_repo, ): with pendulum.test(freeze_datetime): external_sensor = external_repo.get_external_sensor("run_key_sensor") instance.add_job_state( JobState(external_sensor.get_external_origin(), JobType.SENSOR, JobStatus.RUNNING) ) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 0 evaluate_sensors(instance, workspace) wait_for_all_runs_to_start(instance) assert instance.get_runs_count() == 1 run = instance.get_runs()[0] ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SUCCESS, expected_run_ids=[run.run_id], ) # run again (after 30 seconds), to ensure that the run key maintains idempotence freeze_datetime = freeze_datetime.add(seconds=30) with pendulum.test(freeze_datetime): evaluate_sensors(instance, workspace) assert instance.get_runs_count() == 1 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 2 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SKIPPED, ) captured = capfd.readouterr() assert ( 'Skipping 1 run for sensor run_key_sensor already completed with run keys: ["only_once"]' in captured.out ) launched_run = instance.get_runs()[0] # Manually create a new run with the same tags execute_pipeline( the_pipeline, run_config=launched_run.run_config, tags=launched_run.tags, instance=instance, ) # Sensor loop still executes freeze_datetime = freeze_datetime.add(seconds=30) with pendulum.test(freeze_datetime): evaluate_sensors(instance, workspace) ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 3 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SKIPPED, )
def test_wrong_config_sensor(external_repo_context, capfd): freeze_datetime = to_timezone( create_pendulum_time( year=2019, month=2, day=27, hour=23, minute=59, second=59, ), "US/Central", ) with instance_with_sensors(external_repo_context) as ( instance, workspace, external_repo, ): with pendulum.test(freeze_datetime): external_sensor = external_repo.get_external_sensor("wrong_config_sensor") instance.add_job_state( JobState(external_sensor.get_external_origin(), JobType.SENSOR, JobStatus.RUNNING) ) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 0 evaluate_sensors(instance, workspace) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.FAILURE, [], "Error in config for pipeline the_pipeline", ) captured = capfd.readouterr() assert ("Error in config for pipeline the_pipeline") in captured.out # Error repeats on subsequent ticks evaluate_sensors(instance, workspace) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 2 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.FAILURE, [], "Error in config for pipeline the_pipeline", ) captured = capfd.readouterr() assert ("Error in config for pipeline the_pipeline") in captured.out
def test_from_format_with_locale(text, fmt, expected): now = pendulum.datetime(2018, 2, 2) with pendulum.test(now): formatted = pendulum.from_format(text, fmt, locale='fr').isoformat() assert formatted == expected
def test_partitions_for_hourly_schedule_decorators_with_timezone(partition_hours_offset: int): with pendulum.test(create_pendulum_time(2019, 2, 27, 0, 1, 1, tz="US/Central")): start_date = datetime(year=2019, month=1, day=1) # You can specify a start date with no timezone and it will be assumed to be # in the execution timezone @hourly_schedule( pipeline_name="foo_pipeline", start_date=start_date, execution_time=time(hour=0, minute=25), execution_timezone="US/Central", partition_hours_offset=partition_hours_offset, ) def hourly_central_schedule(hourly_time): return {"hourly_time": hourly_time.isoformat()} assert hourly_central_schedule.execution_timezone == "US/Central" _check_partitions( hourly_central_schedule, HOURS_UNTIL_FEBRUARY_27 + 1 - partition_hours_offset, pendulum.instance(start_date, tz="US/Central"), DEFAULT_HOURLY_FORMAT_WITH_TIMEZONE, relativedelta(hours=1), ) valid_time = create_pendulum_time( year=2019, month=1, day=27, hour=1, minute=25, tz="US/Central" ) context_with_valid_time = build_schedule_context(scheduled_execution_time=valid_time) execution_data = hourly_central_schedule.evaluate_tick(context_with_valid_time) assert execution_data.run_requests assert len(execution_data.run_requests) == 1 assert execution_data.run_requests[0].run_config == { "hourly_time": create_pendulum_time(year=2019, month=1, day=27, hour=1, tz="US/Central") .subtract(hours=partition_hours_offset) .isoformat() } # You can specify a start date in a different timezone and it will be transformed into the # execution timezone start_date_with_different_timezone = create_pendulum_time(2019, 1, 1, 0, tz="US/Pacific") @hourly_schedule( pipeline_name="foo_pipeline", start_date=start_date_with_different_timezone, execution_time=time(hour=0, minute=25), execution_timezone="US/Central", partition_hours_offset=partition_hours_offset, ) def hourly_central_schedule_with_timezone_start_time(hourly_time): return {"hourly_time": hourly_time.isoformat()} _check_partitions( hourly_central_schedule_with_timezone_start_time, HOURS_UNTIL_FEBRUARY_27 - 2 # start date is two hours later since it's in PT + 1 - partition_hours_offset, to_timezone(start_date_with_different_timezone, "US/Central"), DEFAULT_HOURLY_FORMAT_WITH_TIMEZONE, relativedelta(hours=1), )
def test_diff_for_humans_other_and_minute(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.add(minutes=1)) == "1 minute before"
def test_failure_recovery_after_run_created(external_repo_context, crash_location, crash_signal, capfd): # Verify that if the scheduler crashes or is interrupted after a run is created, # it will just re-launch the already-created run when it runs again with instance_with_schedules(external_repo_context) as ( instance, _grpc_server_registry, external_repo, ): initial_datetime = create_pendulum_time(year=2019, month=2, day=27, hour=0, minute=0, second=0) frozen_datetime = initial_datetime.add() external_schedule = external_repo.get_external_schedule( "simple_schedule") with pendulum.test(frozen_datetime): instance.start_schedule_and_update_storage_state(external_schedule) debug_crash_flags = { external_schedule.name: { crash_location: crash_signal } } scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, debug_crash_flags], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode != 0 capfd.readouterr() ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 assert ticks[0].status == JobTickStatus.STARTED assert instance.get_runs_count() == 1 if crash_location == "RUN_CREATED": run = instance.get_runs()[0] # Run was created, but hasn't launched yet assert run.tags[ SCHEDULED_EXECUTION_TIME_TAG] == frozen_datetime.isoformat( ) assert run.tags[PARTITION_NAME_TAG] == "2019-02-26" assert run.status == PipelineRunStatus.NOT_STARTED else: # The run was created and launched - running again should do nothing other than # moving the tick to success state. # The fact that we need to add this line indicates that there is still a theoretical # possible race condition - if the scheduler fails after launching a run # and then runs again between when the run was launched and when its status is changed to STARTED by the executor, we could # end up launching the same run twice. Run queueing or some other way to immediately # identify that a run was launched would help eliminate this race condition. For now, # eliminate the possibility by waiting for the run to start before running the # scheduler again. wait_for_all_runs_to_start(instance) run = instance.get_runs()[0] validate_run_started(instance.get_runs()[0], frozen_datetime, create_pendulum_time(2019, 2, 26)) assert run.status in [ PipelineRunStatus.STARTED, PipelineRunStatus.SUCCESS ] frozen_datetime = frozen_datetime.add(minutes=5) with pendulum.test(frozen_datetime): # Running again just launches the existing run and marks the tick as success scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, None], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode == 0 assert instance.get_runs_count() == 1 wait_for_all_runs_to_start(instance) validate_run_started(instance.get_runs()[0], initial_datetime, create_pendulum_time(2019, 2, 26)) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_schedule, initial_datetime, JobTickStatus.SUCCESS, [instance.get_runs()[0].run_id], ) captured = capfd.readouterr() if crash_location == "RUN_CREATED": assert ( "Run {run_id} already created for this execution of simple_schedule" .format(run_id=instance.get_runs()[0].run_id) in captured.out) else: assert ( "Run {run_id} already completed for this execution of simple_schedule" .format(run_id=instance.get_runs()[0].run_id) in captured.out)
def test_diff_for_humans_other_and_hours(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.add(hours=2)) == "2 hours before"
def test_diff_for_humans_now_and_seconds(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.subtract(seconds=2).diff_for_humans() == "a few seconds ago"
def test_diff_for_humans_other_and_nearly_hour(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.add(minutes=59)) == "59 minutes before"
def test_diff_for_humans_other_and_future_second(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans( now.subtract(seconds=1)) == "a few seconds after"
def test_diff_for_humans_other_and_seconds(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.add(seconds=2)) == "a few seconds before"
def test_diff_for_humans_other_and_nearly_future_hour(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans( now.subtract(minutes=59)) == "59 minutes after"
def test_diff_for_humans_other_and_future_second(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(seconds=1)) == "a few seconds after"
def test_diff_for_humans_other_and_nearly_future_hour(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans(now.subtract(minutes=59)) == "59 minutes after"
def test_diff_in_seconds_vs_default_now(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.subtract(hours=1).diff().in_seconds() == 3600
def test_launch_once(external_repo_context, capfd): freeze_datetime = pendulum.datetime( year=2019, month=2, day=27, hour=23, minute=59, second=59, ).in_tz("US/Central") with instance_with_sensors(external_repo_context) as (instance, external_repo): with pendulum.test(freeze_datetime): external_sensor = external_repo.get_external_sensor("run_key_sensor") instance.add_job_state( JobState(external_sensor.get_external_origin(), JobType.SENSOR, JobStatus.RUNNING) ) assert instance.get_runs_count() == 0 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 0 list(execute_sensor_iteration(instance, get_default_daemon_logger("SensorDaemon"))) wait_for_all_runs_to_start(instance) assert instance.get_runs_count() == 1 run = instance.get_runs()[0] ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SUCCESS, expected_run_ids=[run.run_id], ) # run again (after 30 seconds), to ensure that the run key maintains idempotence freeze_datetime = freeze_datetime.add(seconds=30) with pendulum.test(freeze_datetime): list(execute_sensor_iteration(instance, get_default_daemon_logger("SensorDaemon"))) assert instance.get_runs_count() == 1 ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 2 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SKIPPED, ) captured = capfd.readouterr() assert ( f"Run {run.run_id} already completed with the run key `only_once` for run_key_sensor" in captured.out ) launched_run = instance.get_runs()[0] # Manually create a new run with the same tags execute_pipeline( the_pipeline, run_config=launched_run.run_config, tags=launched_run.tags, instance=instance, ) # Sensor loop still executes freeze_datetime = freeze_datetime.add(seconds=30) with pendulum.test(freeze_datetime): list(execute_sensor_iteration(instance, get_default_daemon_logger("SensorDaemon"))) ticks = instance.get_job_ticks(external_sensor.get_external_origin_id()) assert len(ticks) == 3 validate_tick( ticks[0], external_sensor, freeze_datetime, JobTickStatus.SKIPPED, )
def test_diff_for_humans(): with pendulum.test(pendulum.datetime(2016, 8, 29)): diff_for_humans()
def test_failure_recovery_after_tick_success(external_repo_context, crash_location, crash_signal): # Verify that if the scheduler crashes or is interrupted after a run is created, # it will just re-launch the already-created run when it runs again with instance_with_schedules(external_repo_context) as ( instance, _grpc_server_registry, external_repo, ): initial_datetime = create_pendulum_time(year=2019, month=2, day=27, hour=0, minute=0, second=0) frozen_datetime = initial_datetime.add() external_schedule = external_repo.get_external_schedule( "simple_schedule") with pendulum.test(frozen_datetime): instance.start_schedule_and_update_storage_state(external_schedule) debug_crash_flags = { external_schedule.name: { crash_location: crash_signal } } scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, debug_crash_flags], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode != 0 # As above there's a possible race condition here if the scheduler crashes # and launches the same run twice if we crash right after the launch and re-run # before the run actually starts wait_for_all_runs_to_start(instance) assert instance.get_runs_count() == 1 validate_run_started(instance.get_runs()[0], initial_datetime, create_pendulum_time(2019, 2, 26)) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 if crash_signal == get_terminate_signal(): run_ids = [] else: run_ids = [run.run_id for run in instance.get_runs()] validate_tick(ticks[0], external_schedule, initial_datetime, JobTickStatus.STARTED, run_ids) frozen_datetime = frozen_datetime.add(minutes=1) with pendulum.test(frozen_datetime): # Running again just marks the tick as success since the run has already started scheduler_process = multiprocessing.Process( target=_test_launch_scheduled_runs_in_subprocess, args=[instance.get_ref(), frozen_datetime, None], ) scheduler_process.start() scheduler_process.join(timeout=60) assert scheduler_process.exitcode == 0 assert instance.get_runs_count() == 1 validate_run_started(instance.get_runs()[0], initial_datetime, create_pendulum_time(2019, 2, 26)) ticks = instance.get_job_ticks( external_schedule.get_external_origin_id()) assert len(ticks) == 1 validate_tick( ticks[0], external_schedule, initial_datetime, JobTickStatus.SUCCESS, [instance.get_runs()[0].run_id], )
def test_diff_for_humans_now_and_second(): with pendulum.test(pendulum.today().at(12, 34, 56)): now = pendulum.now().time() assert now.diff_for_humans() == "a few seconds ago"
def test_partitions_for_hourly_schedule_decorators_with_timezone(): with instance_for_test() as instance: with pendulum.test( pendulum.create(2019, 2, 27, 0, 1, 1, tz="US/Central")): start_date = datetime(year=2019, month=1, day=1, minute=1) # You can specify a start date with no timezone and it will be assumed to be # in the execution timezone @hourly_schedule( pipeline_name="foo_pipeline", start_date=start_date, execution_time=time(hour=0, minute=25), execution_timezone="US/Central", ) def hourly_central_schedule(hourly_time): return {"hourly_time": hourly_time.isoformat()} assert hourly_central_schedule.execution_timezone == "US/Central" _check_partitions( hourly_central_schedule, HOURS_UNTIL_FEBRUARY_27, pendulum.instance(start_date, tz="US/Central"), DEFAULT_HOURLY_FORMAT_WITH_TIMEZONE, relativedelta(hours=1), ) valid_time = pendulum.create(year=2019, month=1, day=27, hour=1, minute=25, tz="US/Central") context_with_valid_time = ScheduleExecutionContext( instance, valid_time) assert hourly_central_schedule.get_run_config( context_with_valid_time) == { "hourly_time": pendulum.create(year=2019, month=1, day=27, hour=0, minute=1, tz="US/Central").isoformat() } assert hourly_central_schedule.should_execute( context_with_valid_time) # You can specify a start date in a different timezone and it will be transformed into the # execution timezone start_date_with_different_timezone = pendulum.create( 2019, 1, 1, 0, 1, tz="US/Pacific") @hourly_schedule( pipeline_name="foo_pipeline", start_date=start_date_with_different_timezone, execution_time=time(hour=0, minute=25), execution_timezone="US/Central", ) def hourly_central_schedule_with_timezone_start_time(hourly_time): return {"hourly_time": hourly_time.isoformat()} _check_partitions( hourly_central_schedule_with_timezone_start_time, HOURS_UNTIL_FEBRUARY_27 - 2, # start date is two hours later since it's in PT start_date_with_different_timezone.in_tz("US/Central"), DEFAULT_HOURLY_FORMAT_WITH_TIMEZONE, relativedelta(hours=1), )