diff --git a/test/unit/io/alignment_file/alignment_file_format_test_template.hpp b/test/unit/io/alignment_file/alignment_file_format_test_template.hpp index 55d2e0e2f7..bc1a2cd231 100644 --- a/test/unit/io/alignment_file/alignment_file_format_test_template.hpp +++ b/test/unit/io/alignment_file/alignment_file_format_test_template.hpp @@ -12,12 +12,26 @@ #include #include #include -#include +#include +#include #include #include using namespace seqan3; +using sam_fields = fields; + // global variables for reuse alignment_file_input_options input_options; alignment_file_output_options output_options; @@ -141,14 +155,10 @@ TYPED_TEST_P(alignment_file_read, input_concept) TYPED_TEST_P(alignment_file_read, header_sucess) { - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->big_header_input}; - alignment_file_header header{}; - - ASSERT_NO_THROW(format.read(istream, input_options, std::ignore, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); + alignment_file_input fin{istream, TypeParam{}}; + auto & header = fin.header(); EXPECT_EQ(header.format_version, "1.6"); EXPECT_EQ(header.sorting, "coordinate"); @@ -184,8 +194,8 @@ TYPED_TEST_P(alignment_file_read, header_sucess) TYPED_TEST_P(alignment_file_read, read_in_all_data) { - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->verbose_reads_input}; + alignment_file_input fin{istream, this->ref_ids, this->ref_sequences, TypeParam{}}; this->tag_dicts[0]["NM"_tag] = -7; this->tag_dicts[0]["AS"_tag] = 2; @@ -202,196 +212,109 @@ TYPED_TEST_P(alignment_file_read, read_in_all_data) this->tag_dicts[1]["bI"_tag] = std::vector{294967296u}; this->tag_dicts[1]["bf"_tag] = std::vector{3.5f, 0.1f, 43.8f}; - dna5_vector seq; - std::string id; - std::vector qual; - int32_t offset; - std::optional ref_id_in; - std::optional ref_offset; - std::pair>, std::vector>> alignment; - uint16_t flag; - uint8_t mapq; - std::tuple, std::optional, int32_t> mate; - sam_tag_dictionary tag_dict; - - for (size_t i = 0; i < 3; ++i) + size_t i{0}; + for (auto & rec : fin) { - ASSERT_NO_THROW(format.read(istream, input_options, this->ref_sequences, this->header, seq, qual, id, offset, - std::ignore, ref_id_in, ref_offset, alignment, flag, mapq, mate, tag_dict, - std::ignore, std::ignore)); - - EXPECT_EQ(seq, this->seqs[i]); - EXPECT_EQ(id, this->ids[i]); - EXPECT_EQ(qual, this->quals[i]); - EXPECT_EQ(offset, this->offsets[i]); - EXPECT_EQ(ref_id_in, 0); - EXPECT_EQ(*ref_offset, this->ref_offsets[i]); - EXPECT_EQ(get<0>(alignment), get<0>(this->alignments[i])); - EXPECT_EQ(get<1>(alignment), get<1>(this->alignments[i])); - EXPECT_EQ(flag, this->flags[i]); - EXPECT_EQ(mapq, this->mapqs[i]); - EXPECT_EQ(mate, this->mates[i]); - EXPECT_EQ(tag_dict, this->tag_dicts[i]); - - seq.clear(); - id.clear(); - qual.clear(); - offset = 0; - ref_id_in = 0; - ref_offset = 0; - alignment = std::pair>, std::vector>>{}; - flag = 0; - mapq = 0; - mate = std::tuple, std::optional, int32_t>{}; - tag_dict.clear(); + EXPECT_EQ(get(rec), this->seqs[i]); + EXPECT_EQ(get(rec), this->ids[i]); + EXPECT_EQ(get(rec), this->quals[i]); + EXPECT_EQ(get(rec), this->offsets[i]); + EXPECT_EQ(get(rec), 0); + EXPECT_EQ(*get(rec), this->ref_offsets[i]); + EXPECT_TRUE(std::ranges::equal(get<0>(get(rec)), get<0>(this->alignments[i]))); + EXPECT_TRUE(std::ranges::equal(get<1>(get(rec)), get<1>(this->alignments[i]))); + EXPECT_EQ(get(rec), this->flags[i]); + EXPECT_EQ(get(rec), this->mapqs[i]); + EXPECT_EQ(get(rec), this->mates[i]); + EXPECT_EQ(get(rec), this->tag_dicts[i]); + ++i; } } TYPED_TEST_P(alignment_file_read, read_in_all_but_empty_data) { - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->empty_input}; - - dna5_vector seq; - std::string id; - std::vector qual; - int32_t offset; - std::optional ref_id_in; - std::optional ref_offset; - std::pair>, std::vector>> alignment; - uint16_t flag; - uint8_t mapq; - std::tuple, std::optional, int32_t> mate; - sam_tag_dictionary tag_dict; - - ASSERT_NO_THROW(format.read(istream, input_options, this->ref_sequences, this->header, seq, qual, id, offset, std::ignore, - ref_id_in, ref_offset, alignment, flag, mapq, mate, tag_dict, std::ignore, std::ignore)); - - EXPECT_TRUE(seq.empty()); - EXPECT_TRUE(id.empty()); - EXPECT_TRUE(qual.empty()); - EXPECT_EQ(offset, 0); - EXPECT_TRUE(!ref_offset.has_value()); - EXPECT_TRUE(get<0>(alignment).empty()); - EXPECT_TRUE(get<1>(alignment).empty()); - EXPECT_EQ(flag, 0u); - EXPECT_EQ(mapq, 0u); - EXPECT_TRUE(!get<0>(mate).has_value()); - EXPECT_TRUE(!get<1>(mate).has_value()); - EXPECT_EQ(get<2>(mate), int32_t{}); - EXPECT_TRUE(tag_dict.empty()); + alignment_file_input fin{istream, this->ref_ids, this->ref_sequences, TypeParam{}}; + + EXPECT_TRUE(get(*fin.begin()).empty()); + EXPECT_TRUE(get(*fin.begin()).empty()); + EXPECT_TRUE(get(*fin.begin()).empty()); + EXPECT_EQ(get(*fin.begin()), 0); + EXPECT_TRUE(!get(*fin.begin()).has_value()); + EXPECT_TRUE(!get(*fin.begin()).has_value()); + EXPECT_TRUE(std::ranges::empty(get<0>(get(*fin.begin())))); + EXPECT_TRUE(std::ranges::empty(get<1>(get(*fin.begin())))); + EXPECT_EQ(get(*fin.begin()), 0u); + EXPECT_EQ(get(*fin.begin()), 0u); + EXPECT_TRUE(!get<0>(get(*fin.begin())).has_value()); + EXPECT_TRUE(!get<1>(get(*fin.begin())).has_value()); + EXPECT_EQ(get<2>(get(*fin.begin())), int32_t{}); + EXPECT_TRUE(get(*fin.begin()).empty()); } -TYPED_TEST_P(alignment_file_read, read_in_nothing) +TYPED_TEST_P(alignment_file_read, read_in_almost_nothing) { - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->simple_three_reads_input}; + alignment_file_input fin{istream, TypeParam{}, fields{}}; - alignment_file_header header{}; - - for (size_t i = 0; i < 3; ++i) - { - ASSERT_NO_THROW(format.read(istream, input_options, std::ignore, header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); - } + size_t i{0}; + for (auto & [mapq] : fin) + EXPECT_EQ(mapq, this->mapqs[i++]); } TYPED_TEST_P(alignment_file_read, read_in_alignment_only_with_ref) { - std::pair>, std::vector>> alignment; - std::optional ref_id_in; - { - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->simple_three_reads_input}; - /*with reference information*/ - for (size_t i = 0; i < 3; ++i) - { - ASSERT_NO_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_id_in, std::ignore, alignment, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); + alignment_file_input fin{istream, this->ref_ids, this->ref_sequences, TypeParam{}, fields{}}; - EXPECT_EQ(get<0>(alignment), get<0>(this->alignments[i])); - EXPECT_EQ(get<1>(alignment), get<1>(this->alignments[i])); - - alignment = std::pair>, std::vector>>{}; // reset - ref_id_in = 0; + size_t i{0}; + for (auto & [alignment] : fin) + { + EXPECT_TRUE(std::ranges::equal(get<0>(alignment), get<0>(this->alignments[i]))); + EXPECT_TRUE(std::ranges::equal(get<1>(alignment), get<1>(this->alignments[i]))); + ++i; } } { // empty cigar - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->empty_cigar}; - std::istringstream istream_empty_cigar{}; - + alignment_file_input fin{istream, this->ref_ids, this->ref_sequences, TypeParam{}, fields{}}; - ASSERT_NO_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_id_in, std::ignore, alignment, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); - - EXPECT_TRUE(std::ranges::empty(get<0>(alignment))); - EXPECT_TRUE(std::ranges::empty(get<1>(alignment))); + EXPECT_TRUE(std::ranges::empty(get<0>(get(*fin.begin())))); + EXPECT_TRUE(std::ranges::empty(get<1>(get(*fin.begin())))); } } TYPED_TEST_P(alignment_file_read, read_in_alignment_only_without_ref) { - using dummy_type = gap_decorator; - std::optional ref_id_in; - std::pair>> alignment2; - { - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->simple_three_reads_input}; - alignment_file_header<> default_header{}; - - for (size_t i = 0; i < 3; ++i) - { - ASSERT_NO_THROW(format.read(istream, input_options, std::ignore, default_header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_id_in, std::ignore, alignment2, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); - - EXPECT_EQ(get<1>(alignment2), get<1>(this->alignments[i])); + alignment_file_input fin{istream, TypeParam{}, fields{}}; - alignment2 = std::pair>>{}; // reset - ref_id_in = 0; - } + size_t i{0}; + for (auto & [alignment] : fin) + EXPECT_TRUE(std::ranges::equal(get<1>(alignment), get<1>(this->alignments[i++]))); } { // empty cigar - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->empty_cigar}; - alignment_file_header<> default_header{}; - - ASSERT_NO_THROW(format.read(istream, input_options, std::ignore, default_header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_id_in, std::ignore, alignment2, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); + alignment_file_input fin{istream, TypeParam{}, fields{}}; - EXPECT_TRUE(std::ranges::empty(get<0>(alignment2))); - EXPECT_TRUE(std::ranges::empty(get<1>(alignment2))); + EXPECT_TRUE(std::ranges::empty(get<0>(get(*fin.begin())))); + EXPECT_TRUE(std::ranges::empty(get<1>(get(*fin.begin())))); } } TYPED_TEST_P(alignment_file_read, read_mate_but_not_ref_id_with_ref) { - std::tuple, std::optional, int32_t> mate; - { /*with reference information*/ - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->simple_three_reads_input}; + alignment_file_input fin{istream, this->ref_ids, this->ref_sequences, TypeParam{}, fields{}}; - for (size_t i = 0; i < 3; ++i) - { - ASSERT_NO_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, mate, std::ignore, std::ignore, - std::ignore)); - - EXPECT_EQ(mate, this->mates[i]); - mate = std::tuple, std::optional, int32_t>{}; - } + size_t i{0}; + for (auto & [mate] : fin) + EXPECT_EQ(mate, this->mates[i++]); } } @@ -400,49 +323,25 @@ TYPED_TEST_P(alignment_file_read, read_mate_but_not_ref_id_without_ref) std::tuple, std::optional, int32_t> mate; { /*no reference information*/ - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->simple_three_reads_input}; - alignment_file_header<> default_header{}; - - for (size_t i = 0; i < 3; ++i) - { - ASSERT_NO_THROW(format.read(istream, input_options, std::ignore, default_header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, mate, std::ignore, std::ignore, std::ignore)); + alignment_file_input fin{istream, TypeParam{}, fields{}}; - EXPECT_EQ(mate, this->mates[i]); - mate = std::tuple, std::optional, int32_t>{}; - } + size_t i{0}; + for (auto & [mate] : fin) + EXPECT_EQ(mate, this->mates[i++]); } } TYPED_TEST_P(alignment_file_read, format_error_ref_id_not_in_reference_information) { - using dummy_type = gap_decorator; - std::optional ref_id_in; - std::pair>, std::vector>> alignment; - std::pair>> alignment2; - { // with reference information given - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->unknown_ref}; - - EXPECT_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_id_in, std::ignore, alignment, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, this->ref_ids, this->ref_sequences, TypeParam{}}), format_error); } { // with reference information in the header - detail::alignment_file_input_format format; typename TestFixture::stream_type istream{this->unknown_ref_header}; - alignment_file_header<> default_header{}; - - EXPECT_THROW(format.read(istream, input_options, std::ignore, default_header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_id_in, std::ignore, alignment2, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, TypeParam{}}), format_error); } } @@ -452,7 +351,9 @@ TYPED_TEST_P(alignment_file_read, format_error_ref_id_not_in_reference_informati template struct alignment_file_write : public alignment_file_read -{}; +{ + std::ostringstream ostream; +}; TYPED_TEST_CASE_P(alignment_file_write); @@ -463,98 +364,78 @@ TYPED_TEST_P(alignment_file_write, output_concept) TYPED_TEST_P(alignment_file_write, write_empty_members) { - detail::alignment_file_output_format format; - - std::ostringstream ostream; { - alignment_file_header header{std::vector{this->ref_id}}; - header.ref_id_info.push_back({this->ref_seq.size(), ""}); - header.ref_dict[this->ref_id] = 0; + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; using default_align_t = std::pair>, std::span>>; using default_mate_t = std::tuple, int32_t>; - ASSERT_NO_THROW(format.write(ostream, output_options, header, std::string_view{}, std::string_view{}, - std::string_view{}, 0, std::string_view{}, std::string_view{}, - std::optional{std::nullopt}, default_align_t{}, 0, 0, - default_mate_t{}, sam_tag_dictionary{}, 0, 0)); + fout.emplace_back(&(this->header), std::string_view{}, 0, std::string_view{}, -1, 0, default_align_t{}, 0, + default_mate_t{}, std::string_view{}, std::string_view{}, sam_tag_dictionary{}); } - ostream.flush(); - EXPECT_EQ(ostream.str(), this->empty_input); + this->ostream.flush(); + EXPECT_EQ(this->ostream.str(), this->empty_input); } TYPED_TEST_P(alignment_file_write, default_options_all_members_specified) { - detail::alignment_file_output_format format; - - std::ostringstream ostream; - - alignment_file_header header{std::vector{this->ref_id}}; - header.ref_id_info.push_back({this->ref_seq.size(), ""}); - header.ref_dict[this->ref_id] = 0; - this->tag_dicts[0]["NM"_tag] = 7; this->tag_dicts[0]["AS"_tag] = 2; this->tag_dicts[1]["xy"_tag] = std::vector{3,4,5}; - for (size_t i = 0; i < 3; ++i) - ASSERT_NO_THROW(format.write(ostream, output_options, header, this->seqs[i], this->quals[i], this->ids[i], - this->offsets[i], std::string{}, 0, this->ref_offsets[i], this->alignments[i], - this->flags[i], this->mapqs[i], this->mates[i], this->tag_dicts[i], 0, 0)); + { + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; - ostream.flush(); + for (size_t i = 0; i < 3; ++i) + { + ASSERT_NO_THROW(fout.emplace_back(&(this->header), this->ids[i], this->flags[i], 0/*ref_id*/, + this->ref_offsets[i], this->mapqs[i], this->alignments[i], + this->offsets[i], this->mates[i], this->seqs[i], this->quals[i], + this->tag_dicts[i])); + } + } + this->ostream.flush(); - EXPECT_EQ(ostream.str(), this->simple_three_reads_output); + EXPECT_EQ(this->ostream.str(), this->simple_three_reads_output); } TYPED_TEST_P(alignment_file_write, write_ref_id_with_different_types) { - detail::alignment_file_output_format format; - - std::ostringstream ostream; - - alignment_file_header header{std::vector{this->ref_id}}; - header.ref_id_info.push_back({this->ref_seq.size(), ""}); - header.ref_dict[this->ref_id] = 0; - this->tag_dicts[0]["NM"_tag] = 7; this->tag_dicts[0]["AS"_tag] = 2; this->tag_dicts[1]["xy"_tag] = std::vector{3,4,5}; - // header ref_id_type is std::string - - // std::string - ASSERT_NO_THROW(format.write(ostream, output_options, header, this->seqs[0], this->quals[0], this->ids[0], - this->offsets[0], std::string{}, - /*----------------------->*/ this->ref_id, - this->ref_offsets[0], this->alignments[0], - this->flags[0], this->mapqs[0], this->mates[0], this->tag_dicts[0], 0, 0)); - // std::string_view - ASSERT_NO_THROW(format.write(ostream, output_options, header, this->seqs[1], this->quals[1], this->ids[1], - this->offsets[1], std::string{}, - /*----------------------->*/ std::string_view{this->ref_id}, - this->ref_offsets[1], this->alignments[1], - this->flags[1], this->mapqs[1], this->mates[1], this->tag_dicts[1], 0, 0)); - - // view on string - ASSERT_NO_THROW(format.write(ostream, output_options, header, this->seqs[2], this->quals[2], this->ids[2], - this->offsets[2], std::string{}, - /*----------------------->*/ this->ref_id | view::take(20), - this->ref_offsets[2], this->alignments[2], - this->flags[2], this->mapqs[2], this->mates[2], this->tag_dicts[2], 0, 0)); - - ostream.flush(); - - EXPECT_EQ(ostream.str(), this->simple_three_reads_output); + { + // header ref_id_type is std::string + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; + + // std::string + ASSERT_NO_THROW(fout.emplace_back(&(this->header), this->ids[0], this->flags[0], + /*----------------------->*/ this->ref_id, + this->ref_offsets[0], this->mapqs[0], this->alignments[0], this->offsets[0], + this->mates[0], this->seqs[0], this->quals[0], this->tag_dicts[0])); + + // std::string_view + ASSERT_NO_THROW(fout.emplace_back(&(this->header), this->ids[1], this->flags[1], + /*----------------------->*/ std::string_view{this->ref_id}, + this->ref_offsets[1], this->mapqs[1], this->alignments[1], this->offsets[1], + this->mates[1], this->seqs[1], this->quals[1], this->tag_dicts[1])); + + // view on string + ASSERT_NO_THROW(fout.emplace_back(&(this->header), this->ids[2], this->flags[2], + /*----------------------->*/ this->ref_id | view::take(20), + this->ref_offsets[2], this->mapqs[2], this->alignments[2], this->offsets[2], + this->mates[2], this->seqs[2], this->quals[2], this->tag_dicts[2])); + } + + this->ostream.flush(); + + EXPECT_EQ(this->ostream.str(), this->simple_three_reads_output); } TYPED_TEST_P(alignment_file_write, with_header) { - detail::alignment_file_output_format format; - - std::ostringstream ostream; - alignment_file_header header{std::vector{this->ref_id}}; header.sorting = "unknown"; header.grouping = "none"; @@ -579,70 +460,79 @@ TYPED_TEST_P(alignment_file_write, with_header) this->tag_dicts[1]["bI"_tag] = std::vector{294967296u}; this->tag_dicts[1]["bf"_tag] = std::vector{3.5f, 0.1f, 43.8f}; - for (size_t i = 0; i < 3; ++i) - ASSERT_NO_THROW(format.write(ostream, output_options, header, this->seqs[i], this->quals[i], this->ids[i], - this->offsets[i], std::string{}, 0, this->ref_offsets[i], this->alignments[i], - this->flags[i], this->mapqs[i], this->mates[i], this->tag_dicts[i], 0, 0)); + { + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; - ostream.flush(); + for (size_t i = 0; i < 3; ++i) + { + ASSERT_NO_THROW(fout.emplace_back(&header, this->ids[i], this->flags[i], 0/*ref_id*/, + this->ref_offsets[i], this->mapqs[i], this->alignments[i], + this->offsets[i], this->mates[i], this->seqs[i], this->quals[i], + this->tag_dicts[i])); + } + } - EXPECT_EQ(ostream.str(), this->verbose_output); + this->ostream.flush(); + + EXPECT_EQ(this->ostream.str(), this->verbose_output); } TYPED_TEST_P(alignment_file_write, special_cases) { - detail::alignment_file_output_format format; + std::optional rid; - alignment_file_header header{std::vector{this->ref_id}}; - header.ref_id_info.push_back({this->ref_seq.size(), ""}); - header.ref_dict[this->ref_id] = 0; + // write an empty std::optional for ref id and mate + { + std::tuple, std::optional, int32_t> mate{rid, rid, 0}; - std::ostringstream ostream; + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; - // write an empty std::optional for ref offset and mate - std::optional rid; - std::tuple, std::optional, int32_t> mate{rid, rid, 0}; - - EXPECT_NO_THROW(format.write(ostream, output_options, header, this->seqs[0], this->quals[0], this->ids[0], - this->offsets[0], std::string{}, rid, this->ref_offsets[0], this->alignments[0], - this->flags[0], this->mapqs[0], mate, this->tag_dicts[0], 0, 0)); - ostream.flush(); - EXPECT_EQ(ostream.str(), this->special_output); - - ostream = std::ostringstream{}; // clear - format = detail::alignment_file_output_format{}; // clear header_was_written - - // write the ref id and mate ref as string - std::tuple, int32_t> mate_str{"", rid, 0}; - - /*EXPECT_NO_THROW(*/format.write(ostream, output_options, header, this->seqs[0], this->quals[0], this->ids[0], - this->offsets[0], std::string{}, std::string(""), this->ref_offsets[0], - this->alignments[0], this->flags[0], this->mapqs[0], mate_str, - this->tag_dicts[0], 0, 0)/*)*/; - ostream.flush(); - EXPECT_EQ(ostream.str(), this->special_output); + // std::string + ASSERT_NO_THROW(fout.emplace_back(&(this->header), this->ids[0], this->flags[0], rid, + this->ref_offsets[0], this->mapqs[0], this->alignments[0], this->offsets[0], + mate, this->seqs[0], this->quals[0], this->tag_dicts[0])); + + } + + this->ostream.flush(); + EXPECT_EQ(this->ostream.str(), this->special_output); + + this->ostream = std::ostringstream{}; // clear + + { + // write the ref id and mate ref as string + std::tuple, int32_t> mate_str{"", rid, 0}; + + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; + + // std::string + ASSERT_NO_THROW(fout.emplace_back(&(this->header), this->ids[0], this->flags[0], std::string(""), + this->ref_offsets[0], this->mapqs[0], this->alignments[0], this->offsets[0], + mate_str, this->seqs[0], this->quals[0], this->tag_dicts[0])); + + } + + this->ostream.flush(); + EXPECT_EQ(this->ostream.str(), this->special_output); } TYPED_TEST_P(alignment_file_write, format_errors) { - detail::alignment_file_output_format format; - - alignment_file_header header{std::vector{this->ref_id}}; - header.ref_id_info.push_back({this->ref_seq.size(), ""}); - header.ref_dict[this->ref_id] = 0; - - std::ostringstream ostream; + alignment_file_output fout{this->ostream, TypeParam{}, sam_fields{}}; // ensure that only a ref_id that is listed in the header is allowed - EXPECT_THROW(format.write(ostream, output_options, header, this->seqs[0], this->quals[0], this->ids[0], - this->offsets[0], std::string{}, std::string("ref_id_that_does_not_exist"), - this->ref_offsets[0], this->alignments[0], this->flags[0], - this->mapqs[0], this->mates[0], this->tag_dicts[0], 0, 0), + EXPECT_THROW(fout.emplace_back(&(this->header), this->ids[0], this->flags[0], + std::string("ref_id_that_does_not_exist"), + this->ref_offsets[0], this->mapqs[0], this->alignments[0], + this->offsets[0], this->mates[0], this->seqs[0], this->quals[0], + this->tag_dicts[0]), format_error); - EXPECT_THROW(format.write(ostream, output_options, header, this->seqs[0], this->quals[0], this->ids[0], - this->offsets[0], std::string{}, this->ref_id, -3, this->alignments[0], this->flags[0], - this->mapqs[0], this->mates[0], this->tag_dicts[0], 0, 0), + // no negative values except -1 are allowed fot the ref offset + EXPECT_THROW(fout.emplace_back(&(this->header), this->ids[0], this->flags[0], this->ref_id, + -3, this->mapqs[0], this->alignments[0], + this->offsets[0], this->mates[0], this->seqs[0], this->quals[0], + this->tag_dicts[0]), format_error); } @@ -651,7 +541,7 @@ REGISTER_TYPED_TEST_CASE_P(alignment_file_read, header_sucess, read_in_all_data, read_in_all_but_empty_data, - read_in_nothing, + read_in_almost_nothing, read_in_alignment_only_with_ref, read_in_alignment_only_without_ref, read_mate_but_not_ref_id_with_ref, diff --git a/test/unit/io/alignment_file/format_bam_test.cpp b/test/unit/io/alignment_file/format_bam_test.cpp index 28b0bd65f4..7ba5a250e3 100644 --- a/test/unit/io/alignment_file/format_bam_test.cpp +++ b/test/unit/io/alignment_file/format_bam_test.cpp @@ -249,16 +249,8 @@ struct bam_format : public alignment_file_data TEST_F(bam_format, wrong_magic_bytes) { - std::string cam1_bute_str{'\x43', '\x41', '\x4D', '\x01' /*CAM\1*/}; - - std::istringstream stream{cam1_bute_str}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, std::ignore, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); - + std::istringstream stream{std::string{'\x43', '\x41', '\x4D', '\x01' /*CAM\1*/}}; + EXPECT_THROW((alignment_file_input{stream, format_bam{}}), format_error); } TEST_F(bam_format, unknown_ref_in_header) @@ -273,12 +265,7 @@ TEST_F(bam_format, unknown_ref_in_header) }; std::istringstream stream{unknown_ref}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{stream, this->ref_ids, this->ref_sequences, format_bam{}}), format_error); } TEST_F(bam_format, wrong_ref_length_in_header) @@ -293,22 +280,13 @@ TEST_F(bam_format, wrong_ref_length_in_header) }; std::istringstream stream{wrong_ref_length}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{stream, this->ref_ids, this->ref_sequences, format_bam{}}), format_error); } TEST_F(bam_format, wrong_order_in_header) { std::vector rids = {"ref", "raf"}; - alignment_file_header hdr{rids}; - hdr.ref_id_info.emplace_back(34, ""); - hdr.ref_id_info.emplace_back(30, ""); - hdr.ref_dict[hdr.ref_ids()[0]] = 0; - hdr.ref_dict[hdr.ref_ids()[1]] = 1; + std::vector rseqs = {"ATCGAGATCGATCGATCGAGAGCTAGCGATCGAG"_dna5, "ATCGAGATCGATCGATCGAGAGCTAGCGAT"_dna5}; std::string wrong_order{ // raf is first in file but second in hdr // @HD VN:1.6 @@ -324,20 +302,11 @@ TEST_F(bam_format, wrong_order_in_header) }; std::istringstream stream{wrong_order}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, this->ref_sequences, hdr, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{stream, rids, rseqs, format_bam{}}), format_error); } TEST_F(bam_format, wrong_char_as_tag_identifier) { - dna5_vector seq; - std::pair>, std::vector>> alignment; - sam_tag_dictionary tag_dict; - { std::string wrong_char_in_tag{ // Y in CG tag // @HD VN:1.0 @@ -356,12 +325,7 @@ TEST_F(bam_format, wrong_char_as_tag_identifier) }; std::istringstream stream{wrong_char_in_tag}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, seq, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, - std::ignore, std::ignore, tag_dict, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{stream, this->ref_ids, this->ref_sequences, format_bam{}}), format_error); } { std::string wrong_char_in_tag{ // Y in CG:B array tag @@ -381,21 +345,12 @@ TEST_F(bam_format, wrong_char_as_tag_identifier) }; std::istringstream stream{wrong_char_in_tag}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, seq, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, - std::ignore, std::ignore, tag_dict, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{stream, this->ref_ids, this->ref_sequences, format_bam{}}), format_error); } } TEST_F(bam_format, invalid_cigar_op) { - dna5_vector seq; - std::pair>, std::vector>> alignment; - sam_tag_dictionary tag_dict; - { std::string wrong_char_in_tag{// "1D" replaced by "1?" (D is encoded as 2, but 2 was replaced by 14) // @HD VN:1.6 @@ -414,12 +369,7 @@ TEST_F(bam_format, invalid_cigar_op) }; std::istringstream stream{wrong_char_in_tag}; - detail::alignment_file_input_format format{}; - - EXPECT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, seq, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, - std::ignore, std::ignore, std::ignore, tag_dict, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{stream, this->ref_ids, this->ref_sequences, format_bam{}}), format_error); } } @@ -441,42 +391,27 @@ TEST_F(bam_format, too_long_cigar_string_read) '\x31', '\x4D', '\x31', '\x49', '\x00' }; - dna5_vector seq; - std::pair>, std::vector>> alignment; - sam_tag_dictionary tag_dict; - { // successful reading std::istringstream stream{sam_file_with_too_long_cigar_string}; - detail::alignment_file_input_format format{}; - - ASSERT_NO_THROW(format.read(stream, input_options, this->ref_sequences, this->header, seq, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, - std::ignore, std::ignore, tag_dict, std::ignore, std::ignore)); + alignment_file_input fin{stream, this->ref_ids, this->ref_sequences, format_bam{}}; - EXPECT_EQ(get<0>(alignment), get<0>(this->alignments[0])); - EXPECT_EQ(get<1>(alignment), get<1>(this->alignments[0])); - EXPECT_EQ(tag_dict.size(), 0u); // redundant CG tag is removed + EXPECT_TRUE(std::ranges::equal(get<0>(get(*fin.begin())), get<0>(this->alignments[0]))); + EXPECT_TRUE(std::ranges::equal(get<1>(get(*fin.begin())), get<1>(this->alignments[0]))); + EXPECT_EQ(get(*fin.begin()).size(), 0u); // redundant CG tag is removed } { // error: sam_tag_dictionary is not read std::istringstream stream{sam_file_with_too_long_cigar_string}; - detail::alignment_file_input_format format{}; - - ASSERT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, seq, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), format_error); + ASSERT_THROW((alignment_file_input{stream, format_bam{}, fields{}}), format_error); } { // error: sequence is not read std::istringstream stream{sam_file_with_too_long_cigar_string}; - detail::alignment_file_input_format format{}; - - ASSERT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, - std::ignore, std::ignore, tag_dict, std::ignore, std::ignore), format_error); + ASSERT_THROW((alignment_file_input{stream, format_bam{}, fields{}}), + format_error); } { // error no CG tag @@ -495,12 +430,7 @@ TEST_F(bam_format, too_long_cigar_string_read) '\x00', '\x02', '\x02', '\x03' }}; - detail::alignment_file_input_format format{}; - tag_dict.clear(); - - ASSERT_THROW(format.read(stream, input_options, this->ref_sequences, this->header, seq, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, - std::ignore, std::ignore, tag_dict, std::ignore, std::ignore), format_error); + ASSERT_THROW((alignment_file_input{stream, format_bam{}}), format_error); } } @@ -556,13 +486,19 @@ TEST_F(bam_format, too_long_cigar_string_write) header.ref_id_info.push_back({ref.size(), ""}); header.ref_dict[this->ref_id] = 0; - using default_mate_t = std::tuple, int32_t>; - - detail::alignment_file_output_format format; + { + alignment_file_output fout{os, format_bam{}, fields{}}; + + fout.emplace_back(&header, std::string{"long_read"}, read, 0, 0, alignment, 255); + } - format.write(os, output_options, header, read, std::array{}/*empty qual*/, - std::string{"long_read"}, 0/*offset*/, std::string{}, 0/*ref_id*/, 0/*ref_offset*/, alignment, - 0, 255, default_mate_t{}, sam_tag_dictionary{}, 0, 0); + os.flush(); EXPECT_TRUE(os.str() == expected); // do not use EXPECT_EQ because if this fails the output will be huge :D } diff --git a/test/unit/io/alignment_file/format_sam_test.cpp b/test/unit/io/alignment_file/format_sam_test.cpp index ba41a1c174..84b03109e8 100644 --- a/test/unit/io/alignment_file/format_sam_test.cpp +++ b/test/unit/io/alignment_file/format_sam_test.cpp @@ -110,18 +110,13 @@ struct sam_format : public alignment_file_data // since BAM uses the same read header function from SAM, it only needs to be tested once TEST_F(sam_format, header_errors) { - detail::alignment_file_input_format format; - { std::string header_str { "@HD\tVN:1.0\tTT:this is not a valid tag\n" }; std::istringstream istream(header_str); - EXPECT_THROW(format.read(istream, input_options, std::ignore, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } { std::string header_str @@ -129,10 +124,7 @@ TEST_F(sam_format, header_errors) "@HD\tVN:1.0\tSI:this is not a valid tag starting with S\n" }; std::istringstream istream(header_str); - EXPECT_THROW(format.read(istream, input_options, std::ignore, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } { std::string header_str @@ -141,10 +133,7 @@ TEST_F(sam_format, header_errors) "@TT\tthis is not a valid tag\n" }; std::istringstream istream(header_str); - EXPECT_THROW(format.read(istream, input_options, std::ignore, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } { std::string header_str @@ -153,10 +142,7 @@ TEST_F(sam_format, header_errors) "@PG\tID:prog\tTT:this is not a valid tag\n" }; std::istringstream istream(header_str); - EXPECT_THROW(format.read(istream, input_options, std::ignore, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } { std::string header_str @@ -165,10 +151,7 @@ TEST_F(sam_format, header_errors) "@SQ\tSN:unknown_ref\tLN:0\n" }; std::istringstream istream(header_str); - EXPECT_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, this->ref_ids, this->ref_sequences, format_sam{}}), format_error); } { std::string header_str @@ -177,153 +160,78 @@ TEST_F(sam_format, header_errors) "@SQ\tSN:ref\tLN:0\n" /*wrong length*/ }; std::istringstream istream(header_str); - EXPECT_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, this->ref_ids, this->ref_sequences, format_sam{}}), format_error); } } TEST_F(sam_format, windows_file) { - detail::alignment_file_input_format format; - std::string id; std::istringstream istream(std::string("read1\t41\tref\t1\t61\t*\tref\t10\t300\tACGT\t!##$\r\n")); + alignment_file_input fin{istream, format_sam{}, fields{}}; - // with reference sequence information - ASSERT_NO_THROW(format.read(istream, input_options, this->ref_sequences, this->header, std::ignore, std::ignore, - id, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore)); - - EXPECT_EQ(id, std::string{"read1"}); + EXPECT_EQ(get(*fin.begin()), std::string{"read1"}); } - TEST_F(sam_format, format_error_illegal_character_in_seq) { - detail::alignment_file_input_format format; - std::istringstream istream(std::string("*\t0\t*\t0\t0\t*\t*\t0\t0\tAC!T\t*\n")); - alignment_file_header header{}; - std::string seq; - - EXPECT_THROW(format.read(istream, input_options, std::ignore, header, seq, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } TEST_F(sam_format, format_error_invalid_arithmetic_value) { - detail::alignment_file_input_format format; - // invalid value std::istringstream istream(std::string("*\t0\t*\t1abc\t0\t*\t*\t0\t0\t*\t*\n")); - - alignment_file_header header{}; - std::optional ref_offset; - - EXPECT_THROW(format.read(istream, input_options, std::ignore, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_offset, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); // overflow error istream = std::istringstream(std::string("*\t0\t*\t2147483650\t0\t*\t*\t0\t0\t*\t*\n")); - - EXPECT_THROW(format.read(istream, input_options, std::ignore, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_offset, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); // negative value as ref_offset istream = std::istringstream(std::string("*\t0\t*\t-3\t0\t*\t*\t0\t0\t*\t*\n")); - - EXPECT_THROW(format.read(istream, input_options, std::ignore, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, ref_offset, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); // negative value as mate mapping position - std::tuple, std::optional, int32_t> mate; istream = std::istringstream(std::string("*\t0\t*\t0\t0\t*\t*\t-3\t0\t*\t*\n")); - - EXPECT_THROW(format.read(istream, input_options, std::ignore, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - mate, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } TEST_F(sam_format, format_error_invalid_cigar) { - detail::alignment_file_input_format format; - - alignment_file_header header{}; - std::pair>, std::vector>> alignment; - // unkown operation std::istringstream istream(std::string("*\t0\t*\t0\t0\t5Z\t*\t0\t0\t*\t*\n")); - EXPECT_THROW(format.read(istream, input_options, this->ref_sequences, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); // negative number as operation count istream = std::istringstream(std::string("*\t0\t*\t0\t0\t-5M\t*\t0\t0\t*\t*\n")); - EXPECT_THROW(format.read(istream, input_options, this->ref_sequences, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); istream = std::istringstream(std::string("*\t0\t*\t0\t0\t3S4M1I-5M2D2M\t*\t0\t0\t*\t*\n")); - EXPECT_THROW(format.read(istream, input_options, this->ref_sequences, header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, alignment, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } TEST_F(sam_format, format_error_invalid_sam_tag_format) { - detail::alignment_file_input_format format; - // type identifier is wrong std::istringstream istream(std::string("*\t0\t*\t0\t0\t*\t*\t0\t0\t*\t*\tNM:X:3\n")); - - alignment_file_header header{}; - sam_tag_dictionary dict; - - EXPECT_THROW(format.read(istream, input_options, std::ignore, this->header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, dict, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); // Array subtype identifier is wrong istream = std::istringstream(std::string("*\t0\t*\t0\t0\t*\t*\t0\t0\t*\t*\tNM:B:x3,4\n")); - - EXPECT_THROW(format.read(istream, input_options, std::ignore, this->header, std::ignore, std::ignore, std::ignore, - std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, - std::ignore, dict, std::ignore, std::ignore), - format_error); + EXPECT_THROW((alignment_file_input{istream, format_sam{}}), format_error); } TEST_F(sam_format, write_different_header) { std::ostringstream ostream; - alignment_file_header header{std::vector{this->ref_id}}; - header.ref_id_info.push_back({this->ref_seq.size(), ""}); - header.ref_dict[this->ref_id] = 0; - auto write_header = [&] () { - detail::alignment_file_output_format format; - - ASSERT_NO_THROW(format.write(ostream, output_options, header, "", std::vector{}, "", 0, "", 0, 0, - std::pair>, std::vector>>{}, 0, 0, - std::tuple, std::optional, int32_t>{}, - sam_tag_dictionary{}, 0, 0)); + alignment_file_output fout{ostream, format_sam{}, fields{}}; + ASSERT_NO_THROW(fout.emplace_back(&header, this->ref_id, 0)); }; header.sorting = "unsorted";