172 lines
5.6 KiB
Python
172 lines
5.6 KiB
Python
from pathlib import Path
|
|||
|
|
import sys
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[2]
|
||
|
|
if str(ROOT) not in sys.path:
|
||
|
|
sys.path.insert(0, str(ROOT))
|
||
|
|
|
||
|
|
from gfa_core.rnaseq_context import (
|
||
|
|
RnaseqLibraryStrandnessError,
|
||
|
|
apply_rnaseq_context,
|
||
|
|
build_rnaseq_context,
|
||
|
|
featurecounts_strandness_value,
|
||
|
|
find_rnaseq_hisat2_index,
|
||
|
|
find_rnaseq_reference_file,
|
||
|
|
hisat2_rna_strandness_args,
|
||
|
|
require_rnaseq_library_strandness,
|
||
|
|
resolve_rnaseq_library_strandness,
|
||
|
|
resolve_rnaseq_project_context,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_context_prefers_species_output_over_generic_or_reference_prefix(tmp_path):
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
input_dir.mkdir()
|
||
|
|
(input_dir / "samples.yaml").write_text(
|
||
|
|
"""species:
|
||
|
|
code: Os
|
||
|
|
speciesName: Oryza sativa
|
||
|
|
reference:
|
||
|
|
id: osa1_r7
|
||
|
|
rnaseq:
|
||
|
|
outputPrefix: Os
|
||
|
|
samples:
|
||
|
|
S1:
|
||
|
|
r1: S1_1.fastq
|
||
|
|
r2: S1_2.fastq
|
||
|
|
condition: T0
|
||
|
|
""",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
(input_dir / "osa1_r7.gene_models.repr.cds.1.ht2").write_text("cds index", encoding="utf-8")
|
||
|
|
species_dir = input_dir / "Os"
|
||
|
|
species_dir.mkdir()
|
||
|
|
genome = species_dir / "Os.genome.fasta"
|
||
|
|
genome.write_text(">chr1\nACGT\n", encoding="utf-8")
|
||
|
|
(species_dir / "Os.genome.1.ht2").write_text("genome index", encoding="utf-8")
|
||
|
|
|
||
|
|
context = resolve_rnaseq_project_context(
|
||
|
|
input_dir=input_dir,
|
||
|
|
requested_prefix="genome_prepare",
|
||
|
|
sample_config=input_dir / "samples.yaml",
|
||
|
|
)
|
||
|
|
|
||
|
|
assert context["output_prefix"] == "Os"
|
||
|
|
assert context["species_code"] == "Os"
|
||
|
|
assert context["reference_id"] == "osa1_r7"
|
||
|
|
assert find_rnaseq_reference_file(input_dir, "genome", "Os", "osa1_r7") == genome
|
||
|
|
assert find_rnaseq_hisat2_index(input_dir, "Os", "osa1_r7") == species_dir / "Os.genome"
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_reference_lookup_rejects_cds_as_genome(tmp_path):
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
input_dir.mkdir()
|
||
|
|
(input_dir / "osa1_r7.gene_models.repr.cds.fa").write_text(">cds\nACGT\n", encoding="utf-8")
|
||
|
|
(input_dir / "osa1_r7.gene_models.repr.cds.1.ht2").write_text("cds index", encoding="utf-8")
|
||
|
|
|
||
|
|
assert find_rnaseq_reference_file(input_dir, "genome", "Os", "osa1_r7") is None
|
||
|
|
assert find_rnaseq_hisat2_index(input_dir, "Os", "osa1_r7") is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_library_strandness_project_level_is_inherited(tmp_path):
|
||
|
|
sample_config = tmp_path / "samples.yaml"
|
||
|
|
sample_config.write_text(
|
||
|
|
"""rnaseq:
|
||
|
|
library_strandness: reverse
|
||
|
|
samples:
|
||
|
|
S1: {r1: S1_R1.fq.gz, r2: S1_R2.fq.gz}
|
||
|
|
S2: {r1: S2_R1.fq.gz, r2: S2_R2.fq.gz}
|
||
|
|
""",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
info = require_rnaseq_library_strandness(sample_config, ["S1", "S2"])
|
||
|
|
|
||
|
|
assert info["project_library_strandness"] == "reverse"
|
||
|
|
assert info["library_strandness_by_sample"] == {"S1": "reverse", "S2": "reverse"}
|
||
|
|
assert info["library_strandness_values"] == ["reverse"]
|
||
|
|
assert hisat2_rna_strandness_args("reverse") == ["--rna-strandness", "RF"]
|
||
|
|
assert featurecounts_strandness_value("reverse") == "2"
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_library_strandness_sample_level_overrides_project_level(tmp_path):
|
||
|
|
sample_config = tmp_path / "samples.yaml"
|
||
|
|
sample_config.write_text(
|
||
|
|
"""rnaseq:
|
||
|
|
library_strandness: forward
|
||
|
|
samples:
|
||
|
|
S1: {r1: S1_R1.fq.gz, r2: S1_R2.fq.gz}
|
||
|
|
S2: {r1: S2_R1.fq.gz, r2: S2_R2.fq.gz, library_strandness: non_stranded}
|
||
|
|
""",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
info = require_rnaseq_library_strandness(sample_config, ["S1", "S2"])
|
||
|
|
|
||
|
|
assert info["library_strandness_by_sample"] == {"S1": "forward", "S2": "non_stranded"}
|
||
|
|
assert info["library_strandness_values"] == ["forward", "non_stranded"]
|
||
|
|
assert info["library_strandness_mixed"] is True
|
||
|
|
assert hisat2_rna_strandness_args("forward") == ["--rna-strandness", "FR"]
|
||
|
|
assert featurecounts_strandness_value("non_stranded") == "0"
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_library_strandness_missing_is_required_for_align_quant(tmp_path):
|
||
|
|
sample_config = tmp_path / "samples.yaml"
|
||
|
|
sample_config.write_text(
|
||
|
|
"""samples:
|
||
|
|
S1: {r1: S1_R1.fq.gz, r2: S1_R2.fq.gz}
|
||
|
|
""",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
with pytest.raises(RnaseqLibraryStrandnessError, match="缺少 library_strandness"):
|
||
|
|
require_rnaseq_library_strandness(sample_config, ["S1"])
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_library_strandness_invalid_value_is_rejected(tmp_path):
|
||
|
|
sample_config = tmp_path / "samples.yaml"
|
||
|
|
sample_config.write_text(
|
||
|
|
"""samples:
|
||
|
|
S1: {r1: S1_R1.fq.gz, r2: S1_R2.fq.gz, library_strandness: unknown}
|
||
|
|
""",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
with pytest.raises(RnaseqLibraryStrandnessError, match="允许值"):
|
||
|
|
require_rnaseq_library_strandness(sample_config, ["S1"])
|
||
|
|
|
||
|
|
|
||
|
|
def test_rnaseq_library_strandness_context_is_written_to_manifest(tmp_path):
|
||
|
|
sample_config = tmp_path / "samples.yaml"
|
||
|
|
count_matrix = tmp_path / "count_matrix.txt"
|
||
|
|
sample_config.write_text(
|
||
|
|
"""species:
|
||
|
|
code: Os
|
||
|
|
rnaseq:
|
||
|
|
outputPrefix: Os
|
||
|
|
samples:
|
||
|
|
S1: {condition: CK, library_strandness: reverse}
|
||
|
|
S2: {condition: salt, library_strandness: reverse}
|
||
|
|
groups:
|
||
|
|
S1: CK
|
||
|
|
S2: salt
|
||
|
|
""",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
count_matrix.write_text("gene\tS1\tS2\nGene1\t10\t20\n", encoding="utf-8")
|
||
|
|
|
||
|
|
context = build_rnaseq_context(
|
||
|
|
prefix="Os",
|
||
|
|
sample_config=sample_config,
|
||
|
|
count_matrix=count_matrix,
|
||
|
|
stage="rnaseq_quant",
|
||
|
|
)
|
||
|
|
manifest = apply_rnaseq_context({"parameters": {}}, context)
|
||
|
|
|
||
|
|
assert manifest["rnaseq_library_strandness_by_sample"] == {"S1": "reverse", "S2": "reverse"}
|
||
|
|
assert manifest["rnaseq_library_strandness_values"] == ["reverse"]
|
||
|
|
assert manifest["parameters"]["rnaseq_library_strandness_signature"]
|
||
|
|
assert resolve_rnaseq_library_strandness(sample_config, ["S1", "S2"])["library_strandness_signature"]
|