Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(alerter): fix time and timezone issues in alerter tests #1598

Merged
merged 1 commit into from
Feb 15, 2025

Conversation

wyardley
Copy link
Contributor

@wyardley wyardley commented Feb 8, 2025

Fixes #1330

Some tests wouldn't pass locally if TZ was unset, or not set to to, e.g., UTC.

One issue was the fallback to "local" if TZ was unset - this is a valid value internally within simplemonitor, but I don't think valid as a time zone.

Also, having many tests use the local timezone creates some unpredictability in cases where there are hardcoded offsets, or where the test depends on the local timezone having a specific offset from UTC.

Mock TZ as UTC or MST explicitly, and adjust existing tests based on offsets from that where needed.

Fix / unskip some skipped tests

A few comments:

  • I didn't fix all the skipped tests, but hopefully this might help identify fixes for them? They're all unskipped now.
  • These pass for me locally now, but I'm not 100% sure that all the logic is correct, so please review carefully
  • Should confirm that these work in your environment, and that there's no remaining logic that would create tests that are flaky depending on the time of day or other factors.

# compare the offset between UTC and local time. For simplicity and
# predictability, use a time zone that doesn't have daylight savings.
LOCAL_TZ = "MST"
LOCAL_TZ_OFFSET = -7
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just defined these as top level constants vs. in the classes, mostly so that it would be easier to use the @freeze_time decorator in more places.

def test_should_alert_limit(self):
config = {
"times_type": "only",
"time_lower": "10:00",
"time_upper": "11:00",
"limit": 2,
"times_tz": "+07:00",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure if this is the right / desired logic. I think since we're just testing limits, as long as times_tz is set, and the offset is right, that's sufficient to test the behavior you're trying to test here?

@@ -416,12 +419,9 @@ class TestMessageBuilding(unittest.TestCase):
def setUp(self):
self.test_alerter = alerter.Alerter()
self.freeze_time_value = "2020-03-10 09:00"
self.tz = os.environ.get("TZ", "local")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the fallback ("local") was working as expected here and in other spots. I also don't think it's necessary if we force MST for these.

@jamesoff
Copy link
Owner

jamesoff commented Feb 8, 2025

This looks amazing, thanks! I've been finding trying to straighten these tests out a bit mind-bending. I'm on holiday at the moment but will evaluate properly when I'm back based on your notes :)

@wyardley

This comment was marked as outdated.

@wyardley
Copy link
Contributor Author

wyardley commented Feb 8, 2025

I squashed my changes, but check out 22ed7ee if you want to see the changes / evolution.

I think the latest changes should pass in CI.

In particular, I'm not sure the skipped ones where I update times_tz is the right fix.

self.utcoffset = a.utcoffset()
# Force these ones to UTC to simplify; see also TestMessageBuildingTZ class
# Note: For Reasons, the decorator doesn't work well with this.
with mock.patch.dict(os.environ, {"TZ": "UTC"}, clear=True):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this one only, I could induce failures by doing, e.g.,

$ TZ=EST5EDT  poetry run pytest -vvv

somehow, simplemonitor/Alerters/alerter.py still would be reading os.env.TZ from the system in this case. I first tried seeing if I needed to use importlib to reload the libraries being tested, because maybe they were evaluating os.getenv() first, or overriding the decorator for setup, or that it was something related to using unittest test cases within pytest (which I'm guessing is it?); in the end, using a with in the setup routine here seemed to resolve it.

https://adamj.eu/tech/2020/10/13/how-to-mock-environment-variables-with-pythons-unittest/
https://adamj.eu/tech/2020/10/13/how-to-mock-environment-variables-with-pytest/

@wyardley wyardley force-pushed the wyardley/tz branch 2 times, most recently from 275ce5d to 69e1306 Compare February 8, 2025 23:47
@wyardley wyardley mentioned this pull request Feb 8, 2025
@wyardley wyardley changed the title test(alerter): update timezones in tests test(alerter): fix time and timezone issues in alerter tests Feb 9, 2025
@wyardley wyardley force-pushed the wyardley/tz branch 4 times, most recently from d427dd0 to b91074b Compare February 9, 2025 06:43
Fixes jamesoff#1330

Some tests wouldn't pass locally if `TZ` was unset, or not set to to,
e.g., UTC.

One issue was the fallback to `"local"` if `TZ` was unset - this is a
valid value internally within simplemonitor, but I don't think valid as
a time zone.

Also, having many tests use the local timezone creates some
unpredictability in cases where there are hardcoded offsets, or where
the test depends on the local timezone having a specific offset from
UTC.

Mock `TZ` as `UTC` or `MST` explicitly, and adjust existing tests based
on offsets from that where needed.

Fix / unskip some skipped tests
def test_should_alert_no_catchup(self):
"""
Here, `support_catchup` is unset, so catchup notifications shouldn't be
sent.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would check the docstring and behavior of both test_should_alert_no_catchup() and test_should_alert_catchup(). I think I got it right, but please check some of these to make sure they're not evergreen.


# Default to UTC, then override for tests where we need to have timezone
# specific logic.
@mock.patch.dict(os.environ, {"TZ": TZ_UTC}, clear=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had this as MST mostly, but decided it was simpler to use UTC in most cases, except where we're explicitly testing TZ specific logic.

self.assertFalse(a._allowed_time())

# Influence time_tz indirectly, though we could also set it directly in
# alerter.Alerter() below.
@mock.patch.dict(os.environ, {"TZ": TZ_LOCAL}, clear=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if I should leave this comment, and also whether you prefer directly manipulating time_tz in the test or patching os.environ["TZ"], or add tests for both variants?

I'd originally found the static offset being hard-coded into specific tests a little confusing, since it wasn't clear how it related to any of the timezones in use (it didn't at all), but open to suggestion here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no real preference — the original approach was brute force of me trying to make it work and understand how to do testing around it simultaneously, so happy with this for now including the comment :)

@jamesoff
Copy link
Owner

Really appreciate you tackling this stuff :)

@jamesoff jamesoff merged commit 5a27794 into jamesoff:develop Feb 15, 2025
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unit test execution
2 participants