Skip to content

feat: Add sanity check to indexed FASTA file - #1745

Merged
tfenne merged 1 commit into
masterfrom
yf/feat/check_fasta_against_index
Jul 14, 2026
Merged

feat: Add sanity check to indexed FASTA file#1745
tfenne merged 1 commit into
masterfrom
yf/feat/check_fasta_against_index

Conversation

@yfarjoun

@yfarjoun yfarjoun commented May 9, 2025

Copy link
Copy Markdown
Contributor

The last position in the fasta file (according ot the index) must be close to the size of the fasta file itself. A mismatch could indicate a corrupt fasta file, or an in correct index.

(tests included)

Description

Please explain the changes you made here.
Explain the motivation for making this change. What existing problem does the pull request solve?

Things to think about before submitting:

  • Make sure your changes compile and new tests pass locally.
  • Add new tests or update existing ones:
    • A bug fix should include a test that previously would have failed and passes now.
    • New features should come with new tests that exercise and validate the new functionality.
  • Extended the README / documentation, if necessary
  • Check your code style.
  • Write a clear commit title and message
    • The commit message should describe what changed and is targeted at htsjdk developers
    • Breaking changes should be mentioned in the commit message.

Summary by CodeRabbit

  • Bug Fixes
    • Improved subsequence byte-range calculations by using the index’s computed offsets directly.
    • Strengthened indexed FASTA opening with additional consistency validation, rejecting mismatched or truncated FASTA/index pairs while still allowing valid trailing whitespace and files without a final newline.
  • Tests
    • Added FASTA fixtures and expanded scenarios covering extra whitespace, header whitespace, and CRLF variants.
    • Added test coverage for index renaming behavior plus sanity-check cases for mismatched indexes, extra whitespace acceptance, and truncated-file rejection.

@yfarjoun
yfarjoun requested a review from tfenne May 9, 2025 01:48
Comment on lines +162 to +166
final Iterator<FastaSequenceIndexEntry> iterator = FastaSequenceIndex.iterator();
FastaSequenceIndexEntry fastaSequenceIndex = null;
while (iterator.hasNext()) {
fastaSequenceIndex = iterator.next();
}

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.

It kinda sucks to have to do this iteration. I wonder if it would make sense to expand the PR a little bit, and modify FastaSequenceIndex to either store index entries in a pair of (Array/ArrayList, HashMap (instead of LinkedHashMap))? Or just to hold an extra pointer to the final entry in the index?

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.

There are several operations that explicitly take advantage of the linked-list aspect...come to think of it, I my sanity-check only works on a "freshly make" object, since operations such as "rename" remove and add entries, which moved the renamed entry to the "end" of the linked-list.... I only need it to work upon initialization, but I need to add some protections. upshot is that a list would be overkill, I'll go with the reference to the last element.

while (iterator.hasNext()) {
fastaSequenceIndex = iterator.next();
}
assert fastaSequenceIndex != null;

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.

Asserts can be disabled at runtime - are you ok with that, or do you want to always check this?

* @param fastaFile Used for error reporting only.
* @param index index file to check against the dictionary.
*/
public static void sanityCheckFastaAgainstIndex(final String fastaFile,

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.

Why String and not Path or File?

throw new IllegalArgumentException("The fasta file is shorter (%d) than its index claims (%d). Please reindex the fasta.".formatted(fastaLength, lastSequenceEnd));
}
// not sure why need to add 1 here.
if (lastSequenceEnd + fastaSequenceIndex.getTerminatorLength() + 1 < fastaLength) {

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.

Honestly I wonder if we should allow for more than 1? What if there are a handful of blank lines at the end? Maybe we could try a nested solution, that if the file is longer than you have here ... then we read the content after the last base and ensure it's all whitespace?

final long lastSequenceStart = fastaSequenceIndex.getLocation();
final long lastSequenceEnd = lastSequenceStart + fastaSequenceIndex.getOffset(lastSequenceLength);

final long fastaLength = new File(fastaFile).length();

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.

Is this safe to do? Does AbstractIndexedFastaSequenceFile support or have sub-classes that support URL based access (like http:// or ftp://)? If so ... is there a method to call to get the file/object length?

final int basesPerLine = this.getBasesPerLine();
final int bytesPerLine = this.getBytesPerLine();

return ((pos - 1) / basesPerLine) * bytesPerLine + (pos - 1) % basesPerLine;

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.

Suggested change
return ((pos - 1) / basesPerLine) * bytesPerLine + (pos - 1) % basesPerLine;
return ((pos - 1) / basesPerLine) * bytesPerLine + ((pos - 1) % basesPerLine);

Just for clarity.

@yfarjoun

yfarjoun commented May 9, 2025

Copy link
Copy Markdown
Contributor Author

Thanks @tfenne. I responded to all your comments (I think!) please re-review.

BTW, the test failures were due to the FTP site not responding.

@tfenne tfenne left a comment

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.

LGTM - should anyone else review?

final long lastSequenceEnd = lastSequenceStart + lastSequenceIndex.getOffset(lastSequenceLength);

final long fastaLength = Files.size(fastaFile);
//Question: should we worry about files with lots of whitespace in their end?

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.

I wouldn't worry about "lots of whitespace" at the end. I think that is still technically valid fasta.

if (!Character.isWhitespace((char) b)) {
throw new IllegalArgumentException(
("The fasta file %s is too long (relative to the index). In particular has a non-whitespace " +
"character (%c) as a position too great (%d) given the claims of its index (%d)." +

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.

Suggested change
"character (%c) as a position too great (%d) given the claims of its index (%d)." +
"character (%c) at a position (%d) beyond the last base according to its index (%d)." +

("The fasta file %s is too long (relative to the index). In particular has a non-whitespace " +
"character (%c) as a position too great (%d) given the claims of its index (%d)." +
" Please reindex the fasta.")
.formatted(fastaFile.getFileName(), (char) b, posOfInterest + i, lastSequenceEnd));

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.

I would emit the absolute path here

@yfarjoun

Copy link
Copy Markdown
Contributor Author

@lbergelson is @tfenne 's review enough here? who else needs to review?

@lbergelson lbergelson left a comment

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.

This looks sane to me I think. I need to convince myself that it works for fasta.gz though. If it doesn't it should be failing tests here so I'm assuming it works out somehow. I have a few comments. I started it running against GATK's tests to see if it hits any weirrd problems I didn't think of. broadinstitute/gatk#9179

final long fastaLength = Files.size(fastaFile);

if (lastSequenceEnd > fastaLength) {
throw new IllegalArgumentException("The fasta file (%s) is shorter (%d) than its index (%s) claims (%d). Please reindex the fasta.".formatted(fastaFile.toUri().toString(),fastaLength, index.toString(), lastSequenceEnd));

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.

IllegalArgument seems like the wrong exception here. I might declare a new IncompatibleIndexException or something like that, it would make it easier for calling code to deal with this. If for example someone wanted to automatically reindex on a failure.

.formatted(fastaFile.toUri().toString(), (char) b, posOfInterest + i, lastSequenceEnd,index.toString()));
}
}
posOfInterest += channelBuffer.limit();

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.

I think there might be minor bug here. When you clear the buffer it should set limit = capacity, but I don't think readFromPosition guarantees that the as many bytes as possible are read, it's possible for it to return fewer than requested. I think this might have the effect of skipping parts of the fasta file on incomplete reads.

Setting it with position or capturing the return value from readFromPosition would both do the right thing I think.

Unlikely to be a real issue since the bug would be to sometimes fail to detect a mismatch if there are characters embedded partway through a block of white space.

* @param fastaFile Path to fasta file
* @param fastaSequenceIndexes index file to check against the fasta file.
*
* @throws IOException in case of io-error when reading fastaFile

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.

This should say what it throws if it detects and error.

* @param pos the (1-based) position in the contig that is requested
* @return the offset (0-based) from 'location' where pos is located in the file.
*/
public long getOffset(long pos) {

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.

👍

* @return The name of the last entry that was added when parsing the index file. Only guarranteed to be correct just
* after initialization. Protected for access from AbstractIndexedFastaSequenceFile.
*/
protected String getLastSequence() {

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.

I don't love this with the caveats but it's fine if it's clearly labelled.

* Empty, protected constructor for unit testing. Use with care, lastSequence will be incorrect.
*/
protected FastaSequenceIndex() {}
protected FastaSequenceIndex() {

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.

I wonder if this is really only used for unit testing.

@lbergelson

Copy link
Copy Markdown
Member

Hmn, I see a test failure that's not the broken FTP ones.


Gradle suite > Gradle test > htsjdk.samtools.util.SequenceUtilTest > testCalculateNmTag FAILED
    java.lang.IllegalArgumentException: The fasta file file:///home/runner/work/htsjdk/htsjdk/src/test/resources/htsjdk/samtools/SequenceUtil/reference_with_lower_and_uppercase.fasta is too long (relative to the index). In particular has a non-whitespace character (a) at a position (44) beyond the last base (44), according to its index (htsjdk.samtools.reference.FastaSequenceIndex@1f). Please reindex the fasta.
        at htsjdk.samtools.reference.AbstractIndexedFastaSequenceFile.sanityCheckFastaAgainstIndex(AbstractIndexedFastaSequenceFile.java:190)
        at htsjdk.samtools.reference.IndexedFastaSequenceFile.sanityCheckFastaAgainstIndex(IndexedFastaSequenceFile.java:47)
        at htsjdk.samtools.reference.IndexedFastaSequenceFile.<init>(IndexedFastaSequenceFile.java:84)
        at htsjdk.samtools.reference.IndexedFastaSequenceFile.<init>(IndexedFastaSequenceFile.java:116)
        at htsjdk.samtools.reference.ReferenceSequenceFileFactory.getReferenceSequenceFile(ReferenceSequenceFileFactory.java:144)
        at htsjdk.samtools.reference.ReferenceSequenceFileFactory.getReferenceSequenceFile(ReferenceSequenceFileFactory.java:175)
        at htsjdk.samtools.reference.ReferenceSequenceFileFactory.getReferenceSequenceFile(ReferenceSequenceFileFactory.java:105)
        at htsjdk.samtools.reference.ReferenceSequenceFileFactory.getReferenceSequenceFile(ReferenceSequenceFileFactory.java:93)
        at htsjdk.samtools.reference.ReferenceSequenceFileFactory.getReferenceSequenceFile(ReferenceSequenceFileFactory.java:82)
        at htsjdk.samtools.util.SequenceUtilTest.testCalculateNmTag(SequenceUtilTest.java:496)

@lbergelson

Copy link
Copy Markdown
Member

It looks like FastaSequenceIndexCreator uses the no args constructor so now it causes explosions. Example

I think the lastSequence cache needs to be kept up to date or just dynamically checked. I assume there was a performance reason for caching it?

@tfenne tfenne added the feature label Jul 13, 2026
tfenne added a commit that referenced this pull request Jul 14, 2026
When opening an indexed FASTA, cheaply verify that the file is consistent with
its index: the fasta must be at least as long as the last base position the
index claims, and any bytes past the last base must be whitespace. This catches
the common (and otherwise silent) failure where a stale or incorrect .fai causes
the wrong bases to be returned. It reads only the file length and the trailing
bytes rather than the whole file, so it is cheap to run on every open.

Originally #1745 by @yfarjoun; rebased onto current master and reworked so that
the index's last entry is derived on demand from the insertion-ordered entries
(instead of a cached name that went stale for some index-construction paths and
produced false positives), and so the trailing scan advances by the number of
bytes actually read.

Co-authored-by: Tim Fennell <tfenne@tfenne.com>
@tfenne
tfenne force-pushed the yf/feat/check_fasta_against_index branch from b6a1842 to 45c1a73 Compare July 14, 2026 02:43
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a736ff1-2f99-4266-b128-5f4237c3062a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Indexed FASTA reads now use index-derived offsets. Construction validates indexed FASTA content and permits only whitespace after the indexed region. Tests cover mismatched indexes, whitespace, missing newlines, truncation, and index renaming.

Changes

FASTA index validation

Layer / File(s) Summary
Index storage and offset calculation
src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java, src/main/java/htsjdk/samtools/reference/FastaSequenceIndexEntry.java, src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java, src/test/java/htsjdk/samtools/reference/FastaSequenceIndexTest.java
Index entries use ordered storage with contig lookup, renaming preserves order, base offsets are calculated through a helper, and indexed reads use that helper.
FASTA consistency checks
src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java
Construction checks the final indexed base against file length and rejects non-whitespace trailing content or truncated FASTA data.
FASTA consistency regression coverage
src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java, src/test/resources/htsjdk/samtools/reference/header_with_extra_white_space.fasta
Tests and fixtures cover mismatched indexes, trailing whitespace, missing final newlines, and truncation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IndexedFastaSequenceFile
  participant FastaSequenceIndex
  participant FastaSequenceIndexEntry
  participant FASTA
  IndexedFastaSequenceFile->>FastaSequenceIndex: getLastIndexEntry()
  FastaSequenceIndex-->>IndexedFastaSequenceFile: final indexed entry
  IndexedFastaSequenceFile->>FastaSequenceIndexEntry: getOffset(last indexed base)
  FastaSequenceIndexEntry-->>IndexedFastaSequenceFile: expected final base offset
  IndexedFastaSequenceFile->>FASTA: inspect file length and trailing bytes
  FASTA-->>IndexedFastaSequenceFile: content length and trailing bytes
  IndexedFastaSequenceFile-->>IndexedFastaSequenceFile: accept or throw IllegalArgumentException
Loading

Suggested reviewers: tfenne

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a sanity check for indexed FASTA files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yf/feat/check_fasta_against_index

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java (1)

77-88: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Apply the sanity check consistently across constructors.

The new sanityCheckFastaAgainstIndex validation was added to the Path constructor but omitted from this IOPath constructor. To ensure consistent protection against mismatched fasta and index files, consider adding the sanity check here as well.

🛠️ Proposed fix
             if (IOUtil.isBlockCompressed(path.toPath(), true)) {
                 throw new SAMException("Indexed block-compressed FASTA file cannot be handled: " + path);
             }
             this.channel = Files.newByteChannel(path.toPath());
+            sanityCheckFastaAgainstIndex(path.toPath(), index);
         } catch (IOException e) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java` around
lines 77 - 88, Update the IOPath-based IndexedFastaSequenceFile constructor to
invoke sanityCheckFastaAgainstIndex after opening the FASTA channel, matching
the validation performed by the Path constructor. Ensure the check uses the
provided index and preserves the existing block-compression and IOException
handling.
src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java (1)

170-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer using the method parameter fastaSequenceIndex instead of the instance field index.

In the exception messages below (lines 180 and 199), the instance field index is used instead of the fastaSequenceIndex parameter. While they refer to the same object in the current implementation, using the parameter explicitly prevents potential bugs if this method is ever called with an index other than the instance field.

♻️ Proposed refactor

Apply the parameter in the formatted string on line 180:

         if (lastBasePosition > fastaLength) {
             throw new IllegalArgumentException(
                     "The fasta file (%s) is shorter (%d) than its index (%s) claims (%d). Please reindex the fasta."
-                            .formatted(fastaFile.toUri(), fastaLength, index, lastBasePosition));
+                            .formatted(fastaFile.toUri(), fastaLength, fastaSequenceIndex, lastBasePosition));
         }

And similarly on line 199:

                 if (!Character.isWhitespace((char) b)) {
                     throw new IllegalArgumentException(
                             ("The fasta file (%s) is longer than its index (%s) accounts for: found a non-whitespace "
                                             + "character (%c) at position %d, beyond the last base at %d. Please reindex the fasta.")
-                                    .formatted(fastaFile.toUri(), index, (char) b, position + i, lastBasePosition));
+                                    .formatted(fastaFile.toUri(), fastaSequenceIndex, (char) b, position + i, lastBasePosition));
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java`
at line 170, Update sanityCheckFastaAgainstIndex to use its fastaSequenceIndex
parameter, rather than the instance field index, in both exception-message
format arguments around the reported validation failures. Leave the validation
logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java`:
- Around line 177-185: In the FASTA length sanity check, update the
lastBasePosition comparison to reject files where fastaLength equals the last
base index by using an inclusive boundary. Initialize position to the byte
immediately after the last base, using lastBasePosition + 1 rather than
lastSequence.getTerminatorLength(), while preserving the existing trailing-byte
whitespace validation.

---

Nitpick comments:
In
`@src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java`:
- Line 170: Update sanityCheckFastaAgainstIndex to use its fastaSequenceIndex
parameter, rather than the instance field index, in both exception-message
format arguments around the reported validation failures. Leave the validation
logic unchanged.

In `@src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java`:
- Around line 77-88: Update the IOPath-based IndexedFastaSequenceFile
constructor to invoke sanityCheckFastaAgainstIndex after opening the FASTA
channel, matching the validation performed by the Path constructor. Ensure the
check uses the provided index and preserves the existing block-compression and
IOException handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d897416-688f-42c6-b3f6-aceccb0cec9c

📥 Commits

Reviewing files that changed from the base of the PR and between 66f87e0 and 45c1a73.

📒 Files selected for processing (6)
  • src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndexEntry.java
  • src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java
  • src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java
  • src/test/resources/htsjdk/samtools/reference/header_with_extra_white_space.fasta

Comment thread src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java Outdated
tfenne added a commit that referenced this pull request Jul 14, 2026
When opening an indexed FASTA, cheaply verify that the file is consistent with
its index: the fasta must be at least as long as the last base position the
index claims, and any bytes past the last base must be whitespace. This catches
the common (and otherwise silent) failure where a stale or incorrect .fai causes
the wrong bases to be returned. It reads only the file length and the trailing
bytes rather than the whole file, so it is cheap to run on every open.

Originally #1745 by @yfarjoun; rebased onto current master and reworked so that
the index's last entry is derived on demand from the insertion-ordered entries
(instead of a cached name that went stale for some index-construction paths and
produced false positives), and so the trailing scan advances by the number of
bytes actually read.

Co-authored-by: Tim Fennell <tfenne@tfenne.com>
@tfenne
tfenne force-pushed the yf/feat/check_fasta_against_index branch from 45c1a73 to 4168587 Compare July 14, 2026 03:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java`:
- Around line 572-590: Update the IndexedFastaSequenceFile constructor to close
its channel when sanityCheckFastaAgainstIndex throws a RuntimeException, then
rethrow the original exception. Update testSanityCheckRejectsTruncatedFasta
cleanup to remove the generated index and all directory contents before deleting
the temporary directory, using the existing directory-tree cleanup utility if
available.
- Around line 554-570: Update testSanityCheckAcceptsFastaWithoutTrailingNewline
cleanup to remove the generated FastaSequenceIndexCreator .fai file before
deleting the temporary directory, or use the project’s directory-tree deletion
utility. Preserve cleanup in the finally block so the test does not leave
artifacts and directory deletion succeeds.
- Around line 554-570: Update both test methods
testSanityCheckAcceptsFastaWithoutTrailingNewline at
src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java:554-570
and the sibling test at :572-590 to remove the generated .fai file during
cleanup, or use IOUtil.deleteDirectoryTree for complete temporary-directory
cleanup; ensure each finally block can delete the directory without
DirectoryNotEmptyException.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18c81fb2-5e0e-4bf1-ba33-b6c8890fb1fc

📥 Commits

Reviewing files that changed from the base of the PR and between 45c1a73 and 4168587.

📒 Files selected for processing (6)
  • src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndexEntry.java
  • src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java
  • src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java
  • src/test/resources/htsjdk/samtools/reference/header_with_extra_white_space.fasta
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/test/resources/htsjdk/samtools/reference/header_with_extra_white_space.fasta
  • src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java
  • src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java

tfenne added a commit that referenced this pull request Jul 14, 2026
When opening an indexed FASTA, cheaply verify that the file is consistent with
its index: the fasta must be at least as long as the last base position the
index claims, and any bytes past the last base must be whitespace. This catches
the common (and otherwise silent) failure where a stale or incorrect .fai causes
the wrong bases to be returned. It reads only the file length and the trailing
bytes rather than the whole file, so it is cheap to run on every open.

Originally #1745 by @yfarjoun; rebased onto current master and reworked so that
the index's last entry is derived on demand from the insertion-ordered entries
(instead of a cached name that went stale for some index-construction paths and
produced false positives), and so the trailing scan advances by the number of
bytes actually read.

Co-authored-by: Tim Fennell <tfenne@tfenne.com>
@tfenne
tfenne force-pushed the yf/feat/check_fasta_against_index branch from 4168587 to 3f8c5b3 Compare July 14, 2026 03:41
tfenne added a commit that referenced this pull request Jul 14, 2026
When opening an indexed FASTA, cheaply verify that the file is consistent with
its index: the fasta must be at least as long as the last base position the
index claims, and any bytes past the last base must be whitespace. This catches
the common (and otherwise silent) failure where a stale or incorrect .fai causes
the wrong bases to be returned. It reads only the file length and the trailing
bytes rather than the whole file, so it is cheap to run on every open.

Originally #1745 by @yfarjoun; rebased onto current master and reworked so that
the index's last entry is derived on demand from the insertion-ordered entries
(instead of a cached name that went stale for some index-construction paths and
produced false positives), and so the trailing scan advances by the number of
bytes actually read.

Co-authored-by: Tim Fennell <tfenne@tfenne.com>
@tfenne
tfenne force-pushed the yf/feat/check_fasta_against_index branch from 3f8c5b3 to f683d18 Compare July 14, 2026 03:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java (1)

132-132: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Prefer entries.hashCode() directly.

Objects.hash(entries) wraps the argument in a single-element array to compute the hash (effectively computing 31 * 1 + entries.hashCode()). Since you are only hashing one object, delegating directly to entries.hashCode() avoids the unnecessary array allocation and is more idiomatic.

♻️ Proposed refactor
     `@Override`
     public int hashCode() {
-        return Objects.hash(entries);
+        return entries.hashCode();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java` at line 132,
Update the hashCode implementation in FastaSequenceIndex to return
entries.hashCode() directly instead of using Objects.hash(entries), preserving
the existing hash source while avoiding unnecessary wrapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java`:
- Around line 99-105: Update the rename logic in FastaSequenceIndex to
short-circuit when newName matches the entry’s current contig, preserving no-op
renames. Otherwise validate entriesByContig for newName before removing the old
mapping, then perform the existing entry update and map insertion only after
validation succeeds.

---

Nitpick comments:
In `@src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java`:
- Line 132: Update the hashCode implementation in FastaSequenceIndex to return
entries.hashCode() directly instead of using Objects.hash(entries), preserving
the existing hash source while avoiding unnecessary wrapping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dc7006fe-bd6d-4997-a000-a8f572c3b2e4

📥 Commits

Reviewing files that changed from the base of the PR and between 4168587 and f683d18.

📒 Files selected for processing (6)
  • src/main/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFile.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndexEntry.java
  • src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java
  • src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java
  • src/test/resources/htsjdk/samtools/reference/header_with_extra_white_space.fasta
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java
  • src/test/resources/htsjdk/samtools/reference/header_with_extra_white_space.fasta
  • src/main/java/htsjdk/samtools/reference/FastaSequenceIndexEntry.java
  • src/test/java/htsjdk/samtools/reference/AbstractIndexedFastaSequenceFileTest.java

Comment thread src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java Outdated
When opening an indexed FASTA, cheaply verify that the file is consistent with
its index: the fasta must be at least as long as the last base position the
index claims, and any bytes past the last base must be whitespace. This catches
the common (and otherwise silent) failure where a stale or incorrect .fai causes
the wrong bases to be returned. It reads only the file length and the trailing
bytes rather than the whole file, so it is cheap to run on every open.

Originally #1745 by @yfarjoun; rebased onto current master and reworked so that
the index's last entry is derived on demand from the insertion-ordered entries
(instead of a cached name that went stale for some index-construction paths and
produced false positives), and so the trailing scan advances by the number of
bytes actually read.

Co-authored-by: Tim Fennell <tfenne@tfenne.com>
@tfenne
tfenne force-pushed the yf/feat/check_fasta_against_index branch from f683d18 to 42e2a29 Compare July 14, 2026 04:06
@tfenne

tfenne commented Jul 14, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@tfenne
tfenne merged commit 59f8148 into master Jul 14, 2026
5 checks passed
@tfenne
tfenne deleted the yf/feat/check_fasta_against_index branch July 14, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants