Skip to content

TST: Adding test for parseheader#19449

Open
JettHiggins wants to merge 4 commits intoastropy:mainfrom
JettHiggins:test_parse_header
Open

TST: Adding test for parseheader#19449
JettHiggins wants to merge 4 commits intoastropy:mainfrom
JettHiggins:test_parse_header

Conversation

@JettHiggins
Copy link
Copy Markdown
Contributor

Description

This pull request is to add tests for the low-level function parse_header in the fits package.
This is apart of my Gsoc 2026 application

I added Tests for :
expected parsed keywords
making sure keywords are skipped
incomplete header blocks error
header blocks without end error
and short keywords being parsed properly with a synthetic fits file

One thing I didn't know how to handle was the pre-commit kept raising a problem with catching the blind exceptions raised in the parse_headers test. I didn't want to update the parse_headers function myself so I was just wondering how that should be handled. Thanks!

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@JettHiggins JettHiggins requested a review from saimn as a code owner March 16, 2026 21:23
@github-actions
Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

Comment on lines +106 to +108
assert "A" in cards_result
assert "SIMPLE" in cards_result
assert "BITPIX" in cards_result
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would expect a much stricter assert statement here. Something like

Suggested change
assert "A" in cards_result
assert "SIMPLE" in cards_result
assert "BITPIX" in cards_result
cards_result == ["A", "SIMPLE", "BITPIX"]

(this is pseudo code, I don't know that a list of strings is actually expected)

with open(filepath, "rb") as f:
header_str, cards = parse_header(f)

for kw in unexpected_keywords:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems odd. Wouldn't it make more sense to run the same exact assertions whatever the data file ?

Suggested change
for kw in unexpected_keywords:
for kw in {"HISTORY", "COMMENT", "END"}:


def test_parse_header_no_end():
"""Test that exactly one block without END card raises exception"""
with pytest.raises(Exception):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exceptions should be tested, but we need as sharp a check as possible:

  • the exception type should be much more accurate since we know exactly what the problem is
  • we should also match the exact error message as tightly as possible (ideally with match="^...$) to make sure the test don't silently pass when a different exception pops up first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Working on the feedback right now. But right now I don't know how the tests should handle the exception because currently parse_header does

if not block or len(block) < BLOCK_SIZE:
    # header looks incorrect, raising exception to fall back to
    # the full Header parsing
    raise Exception

I could change it to

raise ValueError("Header has an incorrect length")

with open(filepath, "rb") as f:
header_str, cards = parse_header(f)

assert len(header_str) % 2880 == 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this seems odd to me: why should we accept 2880, 0 and 5760 as valid responses here ?

Copy link
Copy Markdown
Contributor Author

@JettHiggins JettHiggins Mar 23, 2026

Choose a reason for hiding this comment

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

I did this because the blocks in the FITS are always padded to the length 2880 regardless of the cards inside. But a header can be more than 1 block, so if the output is not a multiple of 2880 then the parsing is wrong.
But 0 should not be a valid option so Ill have to update that. I'll also leave a quick comment explaining the assertion.

Comment on lines +9 to +14
@pytest.mark.parametrize(
"filename, expected_keywords",
[
("data/blank.fits", ["SIMPLE", "BITPIX", "NAXIS"]),
("data/history_header.fits", ["SIMPLE", "BITPIX", "NAXIS"]),
("data/test0.fits", ["SIMPLE", "BITPIX", "NAXIS", "EXTEND"]),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Quick suggestion. A nice simplification might be.

Suggested change
@pytest.mark.parametrize(
"filename, expected_keywords",
[
("data/blank.fits", ["SIMPLE", "BITPIX", "NAXIS"]),
("data/history_header.fits", ["SIMPLE", "BITPIX", "NAXIS"]),
("data/test0.fits", ["SIMPLE", "BITPIX", "NAXIS", "EXTEND"]),
common_keywords = ["SIMPLE", "BITPIX", "NAXIS"]
@pytest.mark.parametrize(
"filename, expected_keywords",
[
("data/blank.fits", common_keywords),
("data/history_header.fits", common_keywords),
("data/test0.fits", common_keywords + ["EXTEND"]),

Etc

@JettHiggins
Copy link
Copy Markdown
Contributor Author

Here's what I changed:
changed to get_pkg_data_fileobj
add comment and check for header sizes
combined unexpected_keywords and expected_keywords into one keywords test
made short_keyword test more precise by creating the exact ordered dict expected from parse_header
defined the required keywords to the fits file

I didn't make any changes to the exception yet and I was also thinking that the keywords test could be a little more precise if I created some ground truth parse_header output and compared to that (kind of like test_parse_header_short_keyword)

Thanks for the feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants