Skip to content

Fix sending mail in case of success#220

Merged
phracek merged 2 commits intomasterfrom
fix_sending_email_in_case_success
Mar 4, 2026
Merged

Fix sending mail in case of success#220
phracek merged 2 commits intomasterfrom
fix_sending_email_in_case_success

Conversation

@phracek
Copy link
Member

@phracek phracek commented Mar 4, 2026

Summary by CodeRabbit

  • Chores

    • Bumped release image/tag to 0.8.8 across build and packaging workflows; minor script cleanup.
  • Tests

    • Added comprehensive test suite for log downloading and parsing; expanded test dependencies.
  • Documentation

    • Added docstrings and clarified internal reporting and email-generation descriptions.

Signed-off-by: Petr "Stone" Hracek <phracek@redhat.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Updates image tag from 0.8.7 to 0.8.8 in build tooling, augments nightly report logic to handle failed TMT runs via a new flag, adds tests for the TestingFarm log downloader and test deps, and removes a commented-out helper from the nightly script.

Changes

Cohort / File(s) Summary
Version bump
​.github/workflows/build-and-push.yml, Dockerfile.daily-tests, Makefile
Bumped daily-tests image/tag from 0.8.7 to 0.8.8 in CI workflow, Dockerfile env, and Makefile build target.
Nightly tests report
daily_tests/daily_nightly_tests_report.py
Added docstrings and extended store_tmt_logs_to_dict signature with is_failed to handle running/failed/missing states; adjusted control flow and callers (collect_data) to pass the new flag and updated log/message handling.
Log downloader tests & deps
daily_tests/tests/test_tf_log_downloader.py, daily_tests/tox.ini
Added comprehensive pytest suite for TestingFarmLogDownloader (init, request ID extraction, log download, XML/report handling, container logs) and added requests and xmltodict to test deps.
Script cleanup
run_nightly_tests.sh
Removed a previously commented-out generate_passwd_file block; no active runtime behavior changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit nudges tags from old to new,
I hop and nibble logs till each is true 🐇,
Tests bloom bright to catch what slips away,
Fail, run, or missing — all have their say,
I thump for builds that pass at break of day.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Fix sending mail in case of success' is directly related to the main changes in the PR. The commit message and file-level changes show modifications to handle email sending logic when tests succeed, with updates to the store_tmt_logs_to_dict method and related email generation logic. The title accurately reflects the primary intent of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_sending_email_in_case_success

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
daily_tests/daily_nightly_tests_report.py (4)

190-191: ⚠️ Potential issue | 🟠 Major

self.send_email is immediately overwritten.

Line 190 reads from the environment, but line 191 unconditionally sets it to True, making the environment variable ineffective. This appears to be debug code left in.

🐛 Proposed fix—remove the hardcoded override
         self.send_email = os.environ.get("SEND_EMAIL", False)
-        self.send_email = True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daily_tests/daily_nightly_tests_report.py` around lines 190 - 191, The code
currently reads SEND_EMAIL into self.send_email then immediately overwrites it
with True; remove the hardcoded override so that self.send_email remains set
from os.environ.get("SEND_EMAIL", False) (i.e., delete the line that
unconditionally sets self.send_email = True) and ensure any downstream logic
uses self.send_email as configured from the environment; check references to
self.send_email in this class/function to confirm behavior remains correct.

186-187: ⚠️ Potential issue | 🟠 Major

Local variable shadows global DEFAULT_MAILS.

This assignment creates a local variable instead of modifying the module-level DEFAULT_MAILS list. The global list remains empty, so emails won't be added correctly.

🐛 Proposed fix
+        global DEFAULT_MAILS
         if "DEFAULT_MAILS" in os.environ:
             DEFAULT_MAILS = os.environ["DEFAULT_MAILS"].split(",")

Alternatively, use DEFAULT_MAILS.extend() or DEFAULT_MAILS[:] = ... to modify in place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daily_tests/daily_nightly_tests_report.py` around lines 186 - 187, The code
inside the if "DEFAULT_MAILS" in os.environ block creates a new local
DEFAULT_MAILS instead of updating the module-level list; change the assignment
to update the global list in place (e.g., use DEFAULT_MAILS[:] =
os.environ["DEFAULT_MAILS"].split(",") or
DEFAULT_MAILS.extend(os.environ["DEFAULT_MAILS"].split(","))) so the
module-level DEFAULT_MAILS is actually modified; locate the if block that checks
"DEFAULT_MAILS" in os.environ and replace the local assignment with one of these
in-place updates.

189-189: ⚠️ Potential issue | 🔴 Critical

Bug: os.environ() is not callable—use os.environ.get().

os.environ is a mapping, not a function. Calling it will raise TypeError: 'os._Environ' object is not callable.

🐛 Proposed fix
-        if "NIGHTLY_BUILDS_URL" in os.environ:
-            self.nightly_builds_url = os.environ("NIGHTLY_BUILDS_URL", "")
+        if "NIGHTLY_BUILDS_URL" in os.environ:
+            self.nightly_builds_url = os.environ.get("NIGHTLY_BUILDS_URL", "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daily_tests/daily_nightly_tests_report.py` at line 189, The assignment to
self.nightly_builds_url uses os.environ as a callable which raises TypeError;
update the code that sets nightly_builds_url (the line initializing
self.nightly_builds_url) to read the environment variable using
os.environ.get("NIGHTLY_BUILDS_URL", "") or os.getenv("NIGHTLY_BUILDS_URL", "")
instead so it safely returns the default empty string when the variable is
missing.

489-498: ⚠️ Potential issue | 🟠 Major

smtp may be undefined in finally block.

If the SMTP() constructor on line 490 raises an exception, smtp is never assigned, and smtp.close() in the finally block will raise NameError.

🐛 Proposed fix
+        smtp = None
         try:
             smtp = SMTP(self.smtp_server, int(self.smtp_port))
             smtp.set_debuglevel(5)
             smtp.sendmail(send_from, send_to, self.mime_msg.as_string())
         except smtplib.SMTPRecipientsRefused as e:
             print(f"Error sending email(SMTPRecipientsRefused): {e.strerror}")
         except smtplib.SMTPException as e:
             print(f"Error sending email(SMTPException): {e}")
         finally:
-            smtp.close()
+            if smtp:
+                smtp.close()

Or use a context manager: with SMTP(...) as smtp:.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daily_tests/daily_nightly_tests_report.py` around lines 489 - 498, The
finally block can reference an undefined smtp if SMTP(self.smtp_server,
int(self.smtp_port)) raises; update the send logic in
daily_nightly_tests_report.py to either use a context manager (with
SMTP(self.smtp_server, int(self.smtp_port)) as smtp:) around the sendmail call
or initialize smtp = None before the try and in finally check if smtp is not
None before calling smtp.close(); adjust the block that catches
SMTPRecipientsRefused and SMTPException to still work with the chosen approach
and ensure smtp.close() is only called when smtp exists.
🧹 Nitpick comments (1)
daily_tests/daily_nightly_tests_report.py (1)

264-267: dictionary_key may be undefined.

If is_running is False and is_failed is False, dictionary_key is never set, but it's used on line 275. However, line 274 guards against this case. Consider using elif for clarity to make the mutual exclusion explicit.

♻️ Suggested improvement
         if is_running:
             dictionary_key = "tmt_running"
-        if is_failed:
+        elif is_failed:
             dictionary_key = "tmt_failed"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daily_tests/daily_nightly_tests_report.py` around lines 264 - 267, The code
sets dictionary_key based on is_running/is_failed but uses two separate ifs so
dictionary_key may remain unset; change the second conditional to an elif (i.e.,
replace the second `if is_failed:` with `elif is_failed:`) or initialize
dictionary_key to a safe default before these checks so that `dictionary_key`
(used later) is always defined; update the block around the is_running/is_failed
assignments (the variables `is_running`, `is_failed`, and `dictionary_key`)
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@daily_tests/daily_nightly_tests_report.py`:
- Around line 248-263: The code block that builds and appends msg uses mutually
independent ifs (checking not_exists, is_running, is_failed) but never defines
msg when all flags are False, causing a NameError at
self.data_dict["tmt"]["msg"].append(msg); change the control flow to either (a)
chain the conditions with if/elif/elif and provide a final else that sets a
default message, or (b) add an early return when no flag is set; update the
block around the variables not_exists, is_running, is_failed and the append to
ensure msg is always defined before calling
self.data_dict["tmt"]["msg"].append(msg).
- Around line 328-330: The call to
store_tmt_logs_to_dict(self.store_tmt_logs_to_dict(path_dir=path_dir,
test_case=test_case, is_failed=False)) passes is_failed=False while other
boolean flags/defaults remain False, which triggers a NameError for an undefined
msg; fix by removing this call entirely if we don't want to log successful
cases, or alternatively call store_tmt_logs_to_dict with the appropriate
parameters including a valid msg (e.g., msg="passed") and any other required
flags so msg is defined (refer to the store_tmt_logs_to_dict function signature
to supply the missing argument(s)).

---

Outside diff comments:
In `@daily_tests/daily_nightly_tests_report.py`:
- Around line 190-191: The code currently reads SEND_EMAIL into self.send_email
then immediately overwrites it with True; remove the hardcoded override so that
self.send_email remains set from os.environ.get("SEND_EMAIL", False) (i.e.,
delete the line that unconditionally sets self.send_email = True) and ensure any
downstream logic uses self.send_email as configured from the environment; check
references to self.send_email in this class/function to confirm behavior remains
correct.
- Around line 186-187: The code inside the if "DEFAULT_MAILS" in os.environ
block creates a new local DEFAULT_MAILS instead of updating the module-level
list; change the assignment to update the global list in place (e.g., use
DEFAULT_MAILS[:] = os.environ["DEFAULT_MAILS"].split(",") or
DEFAULT_MAILS.extend(os.environ["DEFAULT_MAILS"].split(","))) so the
module-level DEFAULT_MAILS is actually modified; locate the if block that checks
"DEFAULT_MAILS" in os.environ and replace the local assignment with one of these
in-place updates.
- Line 189: The assignment to self.nightly_builds_url uses os.environ as a
callable which raises TypeError; update the code that sets nightly_builds_url
(the line initializing self.nightly_builds_url) to read the environment variable
using os.environ.get("NIGHTLY_BUILDS_URL", "") or
os.getenv("NIGHTLY_BUILDS_URL", "") instead so it safely returns the default
empty string when the variable is missing.
- Around line 489-498: The finally block can reference an undefined smtp if
SMTP(self.smtp_server, int(self.smtp_port)) raises; update the send logic in
daily_nightly_tests_report.py to either use a context manager (with
SMTP(self.smtp_server, int(self.smtp_port)) as smtp:) around the sendmail call
or initialize smtp = None before the try and in finally check if smtp is not
None before calling smtp.close(); adjust the block that catches
SMTPRecipientsRefused and SMTPException to still work with the chosen approach
and ensure smtp.close() is only called when smtp exists.

---

Nitpick comments:
In `@daily_tests/daily_nightly_tests_report.py`:
- Around line 264-267: The code sets dictionary_key based on
is_running/is_failed but uses two separate ifs so dictionary_key may remain
unset; change the second conditional to an elif (i.e., replace the second `if
is_failed:` with `elif is_failed:`) or initialize dictionary_key to a safe
default before these checks so that `dictionary_key` (used later) is always
defined; update the block around the is_running/is_failed assignments (the
variables `is_running`, `is_failed`, and `dictionary_key`) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7978c893-a1eb-4e01-bfb9-7f2786727a4e

📥 Commits

Reviewing files that changed from the base of the PR and between b015721 and e19aa99.

📒 Files selected for processing (7)
  • .github/workflows/build-and-push.yml
  • Dockerfile.daily-tests
  • Makefile
  • daily_tests/daily_nightly_tests_report.py
  • daily_tests/tests/test_tf_log_downloader.py
  • daily_tests/tox.ini
  • run_nightly_tests.sh

in case nothing is set only send to pastebin log

Signed-off-by: Petr "Stone" Hracek <phracek@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@daily_tests/daily_nightly_tests_report.py`:
- Around line 163-167: The function load_mails_from_environment currently prints
the entire process environment via print(os.environ), exposing secrets; remove
that full environment dump and instead read only the specific expected variables
(e.g., lookup AUTHORIZED_EMAILS, MAIL_LIST, or whatever keys the function needs)
using os.getenv or os.environ.get, validate/parse them, and if logging is
necessary emit minimal non-sensitive info (like which keys were found or counts)
rather than the full os.environ; update references in
load_mails_from_environment to use those specific env var names and avoid
printing the whole environment.
- Around line 329-332: The loop iterates over success_logs but never uses the
loop variable suc, causing store_tmt_logs_to_dict to be called repeatedly with
the same arguments; either move the call to
store_tmt_logs_to_dict(path_dir=path_dir, test_case=test_case, is_failed=False)
out of the for suc in success_logs loop so it runs once, or if you intended
per-success-log behavior, change store_tmt_logs_to_dict to accept the individual
success entry (e.g., suc) and pass it in (update call sites and the function
signature inside store_tmt_logs_to_dict accordingly); locate and modify the loop
containing success_logs and the store_tmt_logs_to_dict invocation to implement
one of these fixes.
- Around line 254-277: The code can reference an uninitialized dictionary_key
when only not_exists is true; update the conditional logic around
not_exists/is_running/is_failed (the block that sets msg and dictionary_key) to
ensure dictionary_key is always assigned — e.g., add an explicit assignment for
the not_exists case (such as "tmt_missing") or convert the separate ifs to an
if/elif/elif chain so exactly one branch sets dictionary_key before
self.data_dict["tmt"][dictionary_key].append(test_case) is used; keep references
to test_case, msg, and log_name handling the same.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da049c14-ade7-493b-9e8c-0ee50eaa6273

📥 Commits

Reviewing files that changed from the base of the PR and between e19aa99 and b9183d6.

📒 Files selected for processing (1)
  • daily_tests/daily_nightly_tests_report.py

Comment on lines 329 to 332
for suc in success_logs:
self.store_tmt_logs_to_dict(
path_dir=path_dir, test_case=test_case
path_dir=path_dir, test_case=test_case, is_failed=False
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid repeated pastebin uploads in success loop.

Lines [329]-[332] call store_tmt_logs_to_dict once per success log, but the call doesn’t use suc and re-sends the same TMT log repeatedly.

♻️ Suggested fix
-                    for suc in success_logs:
-                        self.store_tmt_logs_to_dict(
-                            path_dir=path_dir, test_case=test_case, is_failed=False
-                        )
+                    self.store_tmt_logs_to_dict(
+                        path_dir=path_dir,
+                        test_case=test_case,
+                    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for suc in success_logs:
self.store_tmt_logs_to_dict(
path_dir=path_dir, test_case=test_case
path_dir=path_dir, test_case=test_case, is_failed=False
)
self.store_tmt_logs_to_dict(
path_dir=path_dir,
test_case=test_case,
)
🧰 Tools
🪛 Ruff (0.15.2)

[warning] 329-329: Loop control variable suc not used within loop body

Rename unused suc to _suc

(B007)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@daily_tests/daily_nightly_tests_report.py` around lines 329 - 332, The loop
iterates over success_logs but never uses the loop variable suc, causing
store_tmt_logs_to_dict to be called repeatedly with the same arguments; either
move the call to store_tmt_logs_to_dict(path_dir=path_dir, test_case=test_case,
is_failed=False) out of the for suc in success_logs loop so it runs once, or if
you intended per-success-log behavior, change store_tmt_logs_to_dict to accept
the individual success entry (e.g., suc) and pass it in (update call sites and
the function signature inside store_tmt_logs_to_dict accordingly); locate and
modify the loop containing success_logs and the store_tmt_logs_to_dict
invocation to implement one of these fixes.

@phracek phracek merged commit 92aaf07 into master Mar 4, 2026
9 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 5, 2026
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.

1 participant