From 64ee1f05d5d41d7783164dd2042495906e5db1e2 Mon Sep 17 00:00:00 2001 From: Thierry Gosselin Date: Sun, 6 Sep 2026 14:42:01 +1000 Subject: [PATCH 1/2] Fail explicitly when paired-end read counts differ Replace the warning-and-stop behaviour at the existing paired-pack count check with an explicit error and nonzero exit status. Include both input filenames in the error message instead of silently discarding unmatched reads. This addresses detected count mismatches in separate paired-end files. It does not add identifier validation or change interleaved input handling. --- src/peprocessor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/peprocessor.cpp b/src/peprocessor.cpp index c3355525..3b41fbdd 100644 --- a/src/peprocessor.cpp +++ b/src/peprocessor.cpp @@ -365,8 +365,8 @@ bool PairEndProcessor::processPairEnd(ReadPack* leftPack, ReadPack* rightPack, T cerr << "WARNING: different read numbers of the " << mPackProcessedCounter << " pack" << endl; cerr << "Read1 pack size: " << leftPack->count << endl; cerr << "Read2 pack size: " << rightPack->count << endl; - cerr << "Ignore the unmatched reads" << endl << endl; - shouldStopReading = true; + error_exit("Paired-end input files contain different numbers of reads: " + + mOptions->in1 + " and " + mOptions->in2); } int tid = config->getThreadId(); From 1773d7843c188c20ae23b2b612bbc33d1da62b15 Mon Sep 17 00:00:00 2001 From: Thierry Gosselin Date: Sun, 6 Sep 2026 14:45:34 +1000 Subject: [PATCH 2/2] Add regression tests for paired-end read-count mismatches Test both shorter-mate directions around pack boundaries and with inputs exceeding 40,000 reads, using one and four threads. Require a nonzero exit status and an explicit count-mismatch diagnostic. Verify that equal inputs and intentional reads_to_process limits continue to succeed. All 20 cases pass with the fix. Unmodified Conda fastp 1.3.6 returns success for all 12 unequal-input cases. --- scripts/test_paired_read_counts.py | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 scripts/test_paired_read_counts.py diff --git a/scripts/test_paired_read_counts.py b/scripts/test_paired_read_counts.py new file mode 100644 index 00000000..70a9ea54 --- /dev/null +++ b/scripts/test_paired_read_counts.py @@ -0,0 +1,41 @@ +"""Run: python3 scripts/test_paired_read_counts.py ./fastp (POSIX).""" +import pathlib +import subprocess +import tempfile +import sys + +exe = str(pathlib.Path(sys.argv[1]).resolve()) +failures = 0 +with tempfile.TemporaryDirectory(prefix='fastp-pairs-') as tmp: + root = pathlib.Path(tmp) + for threads in (1, 4): + for n, m, limit in ((999,999,0),(1000,1000,0),(1001,1001,0), + (999,1000,0),(1000,999,0),(1000,1001,0), + (1001,1000,0),(41000,41001,0),(41001,41000,0), + (1000,1001,500)): + paths = [root/'r1.fq', root/'r2.fq'] + for mate, count in enumerate((n,m)): + paths[mate].write_text(''.join('@r%d/%d\n%s\n+\n%s\n' % + (i,mate+1,'ACGT'*10,'I'*40) for i in range(count))) + cmd = [exe,'-i',str(paths[0]),'-I',str(paths[1]),'-o',str(root/'o1.fq'), + '-O',str(root/'o2.fq'),'-w',str(threads),'-A','-Q','-L','-G', + '--dont_eval_duplication','-j','/dev/null','-h','/dev/null'] + if limit: + cmd += ['--reads_to_process',str(limit)] + try: + run = subprocess.run(cmd,capture_output=True,timeout=5) + if n != m and not limit: + ok = (run.returncode > 0 and + b'Paired-end input files contain different numbers of reads' in run.stderr) + else: + expected = limit or n + ok = run.returncode == 0 + for mate in (1, 2): + output = root/('o%d.fq' % mate) + ok = ok and output.exists() and output.read_bytes().count(b'\n') == expected*4 + failures += not ok + print('PASS' if ok else 'FAIL',n,m,threads,limit,'exit',run.returncode,flush=True) + except subprocess.TimeoutExpired: + failures += 1 + print(n,m,threads,limit,'TIMEOUT',flush=True) +sys.exit(bool(failures))