diff --git a/include/bio/format/fasta.hpp b/include/bio/format/fasta.hpp index 253626a..f063767 100644 --- a/include/bio/format/fasta.hpp +++ b/include/bio/format/fasta.hpp @@ -7,7 +7,7 @@ // ----------------------------------------------------------------------------------------------------- /*!\file - * brief Provides the seqan3::format_fasta. + * \brief Provides the bio::fasta. * \author Hannes Hauswedell */ diff --git a/include/bio/format/fastq.hpp b/include/bio/format/fastq.hpp new file mode 100644 index 0000000..e9b6e27 --- /dev/null +++ b/include/bio/format/fastq.hpp @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik +// Copyright (c) 2020-2021, deCODE Genetics +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------- + +/*!\file + * \brief Provides the bio::fastq. + * \author Hannes Hauswedell + */ + +#pragma once + +#include +#include + +#include + +namespace bio +{ + +/*!\brief The FastQ format. + * \ingroup format + * + * \details + * + * This is the FastQ format tag. If you want to read FastQ files, use bio::seq_io::reader, and if you want + * to write FastQ files, use bio::seq_io::writer. + * + * ### Introduction + * + * FastQ is the de-facto-standard for read data in bionformatics. See the + * [article on wikipedia](https://en.wikipedia.org/wiki/FASTQ_format) for a an in-depth description of the format. + * + * ### Fields + * + * The FastQ format provides the fields bio::field::seq, bio::field::id and bio::field::qual. + * All fields are required when writing. + * + * ### Implementation notes + * + * The following optional features are supported by the implementation: + * + * * Second ID line as third line (after `+`). + * + * The following optional features are *not* supported by the implementation: + * + * * Linebreaks within any field. + */ +struct fastq +{ + //!\brief The valid file extensions for this format; note that you can modify this value. + static inline std::vector file_extensions{"fastq", "fq"}; +}; + +} // namespace bio diff --git a/include/bio/format/fastq_input_handler.hpp b/include/bio/format/fastq_input_handler.hpp new file mode 100644 index 0000000..f652b02 --- /dev/null +++ b/include/bio/format/fastq_input_handler.hpp @@ -0,0 +1,252 @@ +// ----------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik +// Copyright (c) 2020-2021, deCODE Genetics +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------- + +/*!\file + * \brief Provides the bio::format_input_handler implementation for bio::fastq. + * \author Hannes Hauswedell + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace bio +{ + +/*!\brief Format input handler for the FastQ format (bio::fastq). + * \ingroup format + * \details + * + * ### Attention + * + * Most users should not perform I/O through input/output handlers but should instead use the respective + * readers/writers. See the overview (TODO link) for more information. + * + * ### Options + * + * The following options are considered if the respective member variable is availabele in the object passed to + * the constructor: + * + * | Member | Type | Default | Description | + * |-----------------|---------|---------|-----------------------------------------------------| + * |`truncate_ids` |`bool` | `false` | Whether to truncate IDs on the first whitespace | + * + * ### Performance + * + * The current implementation is not optimised for performance and performs an unnecessary copy operation––even + * when shallow records are returned. + */ +template <> +class format_input_handler : public format_input_handler_base> +{ +private: + /*!\name CRTP related entities + * \{ + */ + //!\brief The type of the CRTP base class. + using base_t = format_input_handler_base>; + using base_t::parse_field; + using base_t::parse_field_aux; + using base_t::stream; + + //!\brief Befriend the base class to enable CRTP. + friend base_t; + //!\} + + //!\brief Print an error message with current line number in diagnostic. + [[noreturn]] void error(auto const &... messages) const + { + std::string message = "[B.I.O. FastQ format error in line " + detail::to_string(line) + "] "; + ((message += detail::to_string(messages)), ...); + + throw parse_error{message}; + } + + /*!\name Options + * \{ + */ + //!\brief Whether to truncate IDs on first whitespace. + bool truncate_ids = false; + //!\} + + /*!\name Raw record handling + * \{ + */ + //!\brief The fields that this format supports [the base class accesses this type]. + using format_fields = vtag_t; + //!\brief Type of the raw record. + using raw_record_type = record>; + //!\brief Type of the low-level iterator. + using lowlevel_iterator = detail::plaintext_input_iterator; + + //!\brief The raw record. + raw_record_type raw_record; + //!\brief Buffer for the ID. + std::string id_buffer; + //!\brief Buffer for the sequence. + std::string seq_buffer; + //!\brief Buffer for the qualities. + std::string qual_buffer; + + //!\brief The low-level iterator. + lowlevel_iterator it; + //!\brief A line counter. + size_t line = -1; + + //!\brief Read the raw record [the base class invokes this function]. + void read_raw_record() + { + id_buffer.clear(); + seq_buffer.clear(); + qual_buffer.clear(); + + /* READ ID */ + { + ++it; + ++line; + + if (it == std::default_sentinel) + error("Reached end of file while trying to read ID."); + + std::string_view current_line = *it; + + if (current_line.empty()) + error("Expected to be on begin of record but line is empty."); + + if ((!seqan3::is_char<'@'>)(current_line[0])) + error("ID-line does not begin with '@'."); + + if (truncate_ids) + { + size_t e = 1; + for (; e < current_line.size() && (!seqan3::is_space)(current_line[e]); ++e) + {} + detail::string_copy(current_line.substr(1, e - 1), id_buffer); + } + else + { + detail::string_copy(current_line.substr(1), id_buffer); + } + + get(raw_record) = id_buffer; + } + + /* READ SEQ */ + { + ++it; + ++line; + + if (it == std::default_sentinel) + error("Reached end of file while trying to read SEQ."); + + detail::string_copy(*it, seq_buffer); // is allowed to be empty + + get(raw_record) = seq_buffer; + } + + /* READ third line */ + { + ++it; + ++line; + + if (it == std::default_sentinel) + error("Reached end of file while trying to read third FastQ record line."); + + if ((!seqan3::is_char<'+'>)((*it)[0])) + error("Third FastQ record line does not begin with '+'."); + + // we don't process the rest of the line + } + + /* READ QUAL */ + { + ++it; + ++line; + + if (it == std::default_sentinel) + error("Reached end of file while trying to read QUALITIES."); + + detail::string_copy(*it, qual_buffer); // is allowed to be empty + + get(raw_record) = qual_buffer; + } + + if (size_t ssize = seq_buffer.size(), qsize = qual_buffer.size(); ssize != qsize) + error("Size mismatch between sequence (", ssize, ") and qualities (", qsize, ")."); + } + //!\} + + /*!\name Parsed record handling + * \brief This is mostly done via the defaults in the base class. + * \{ + */ + //!\brief We can prevent another copy if the user wants a string. + void parse_field(vtag_t const & /**/, std::string & parsed_field) { std::swap(id_buffer, parsed_field); } + + //!\brief We can prevent another copy if the user wants a string. + void parse_field(vtag_t const & /**/, std::string & parsed_field) + { + std::swap(seq_buffer, parsed_field); + } + + //!\brief We can prevent another copy if the user wants a string. + void parse_field(vtag_t const & /**/, std::string & parsed_field) + { + std::swap(qual_buffer, parsed_field); + } + //!\} + +public: + /*!\name Constructors, destructor and assignment. + * \{ + */ + format_input_handler() = default; //!< Defaulted. + format_input_handler(format_input_handler const &) = delete; //!< Deleted. + format_input_handler(format_input_handler &&) = default; //!< Defaulted. + ~format_input_handler() = default; //!< Defaulted. + format_input_handler & operator=(format_input_handler const &) = delete; //!< Deleted. + format_input_handler & operator=(format_input_handler &&) = default; //!< Defaulted. + + /*!\brief Construct with an options object. + * \param[in,out] str The input stream. + * \param[in] options An object with options for the input handler. + * \details + * + * The options argument is typically bio::seq_io::reader_options, but any object with a subset of similarly named + * members is also accepted. See bio::format_input_handler for the supported options and defaults. + */ + format_input_handler(std::istream & str, auto const & options) : base_t{str}, it{str, false} + { + if constexpr (requires { (bool)options.truncate_ids; }) + { + truncate_ids = options.truncate_ids; + } + } + + //!\brief Construct with only an input stream. + format_input_handler(std::istream & str) : format_input_handler{str, int{}} {} + //!\} +}; + +} // namespace bio diff --git a/include/bio/seq_io/reader.hpp b/include/bio/seq_io/reader.hpp index edc1f97..79a6465 100644 --- a/include/bio/seq_io/reader.hpp +++ b/include/bio/seq_io/reader.hpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace bio::seq_io @@ -54,7 +55,10 @@ namespace bio::seq_io * * And it supports the following formats: * - * 1. FASTA (see also bio::fasta) + * 1. FastA (see also bio::fasta) + * 2. FastQ (see also bio::fastq) + * + * Fields that are not present in a format (e.g. bio::field::qual in FastA) will be returned empty. * * ### Simple usage * @@ -95,14 +99,12 @@ class reader : public reader_base> { private: //!\brief The base class. - using base_t = reader_base>; + using base_t = reader_base>; + +public: //!\brief Inherit the format_type definition. using format_type = typename base_t::format_type; - /* Implementation note - * format_type is "inherited" as private here to avoid appearing twice in the documentation. - * Its actual visibility is public because it is public in the base class. - */ -public: + // clang-format off //!\copydoc bio::reader_base::reader_base(std::filesystem::path const & filename, format_type const & fmt, options_t const & opt = options_t{}) // clang-format on diff --git a/include/bio/seq_io/reader_options.hpp b/include/bio/seq_io/reader_options.hpp index 6e4a9c6..c706e59 100644 --- a/include/bio/seq_io/reader_options.hpp +++ b/include/bio/seq_io/reader_options.hpp @@ -30,11 +30,11 @@ #include #include #include +#include #include #include #include -// TODO replace seqan3::views::char_strictly_to with seqan3::views::char_strictly_to namespace bio::seq_io { @@ -106,19 +106,6 @@ inline constinit auto field_types_protein = field_types; - -/*!\brief The field types for raw I/O. - * \details - * - * Every field is configured as a std::span of std::byte (this enables "raw" io). - * - * ATTENTION: The exact content of this byte-span depends on the format and is likely not - * compatible between formats. Use at your own risk! - */ -inline constinit auto field_types_raw = - ttag, std::span, std::span>; -// TODO use seqan3::list_traits::repeat as soon as available - //!\} //!\} @@ -183,7 +170,7 @@ inline constinit auto field_types_raw = */ template > + typename formats_t = seqan3::type_list> struct reader_options { /*!\brief The fields that shall be contained in each record; a bio::vtag over bio::field. @@ -208,7 +195,7 @@ struct reader_options * * See seqan3::seq_io::reader for an overview of the the supported formats. */ - formats_t formats = ttag; + formats_t formats = ttag; //!\brief Options that are passed on to the internal stream oject. transparent_istream_options stream_options{}; diff --git a/test/unit/format/CMakeLists.txt b/test/unit/format/CMakeLists.txt index 5b2892a..674cb11 100644 --- a/test/unit/format/CMakeLists.txt +++ b/test/unit/format/CMakeLists.txt @@ -1,5 +1,6 @@ bio_test(bcf_input_test.cpp) bio_test(bcf_output_test.cpp) bio_test(fasta_input_test.cpp) +bio_test(fastq_input_test.cpp) bio_test(vcf_input_test.cpp) bio_test(vcf_output_test.cpp) diff --git a/test/unit/format/fastq_input_test.cpp b/test/unit/format/fastq_input_test.cpp new file mode 100644 index 0000000..3109626 --- /dev/null +++ b/test/unit/format/fastq_input_test.cpp @@ -0,0 +1,317 @@ +// ----------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2020, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------- + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +using seqan3:: operator""_dna5; +using seqan3:: operator""_phred42; +using std::literals::string_view_literals::operator""sv; + +// ---------------------------------------------------------------------------- +// fixture +// ---------------------------------------------------------------------------- + +struct read : public ::testing::Test +{ + using default_rec_t = + bio::record, + seqan3::type_list), + decltype(std::string_view{} | seqan3::views::char_strictly_to)>>; + + std::string default_input = + R"raw(@ID1 +ACGTTTTTTTTTTTTTTT ++ +!##$%&'()*+,-./++- +@ID2 +ACGTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT ++ +!##$&'()*+,-./+)*+,-)*+,-)*+,-)*+,BDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDE +@ID3 lala +ACGTTTA ++ +!!!!!!! +)raw"; + + std::vector ids{ + {"ID1"}, + {"ID2"}, + {"ID3 lala"}, + }; + + std::vector> seqs{ + {"ACGTTTTTTTTTTTTTTT"_dna5}, + {"ACGTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"_dna5}, + {"ACGTTTA"_dna5}, + }; + + std::vector> quals{ + {"!##$%&'()*+,-./++-"_phred42}, + {"!##$&'()*+,-./+)*+,-)*+,-)*+,-)*+,BDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDE"_phred42}, + {"!!!!!!!"_phred42}, + }; + + template + void do_read_test_impl(std::string const & input) + { + std::istringstream istream{input}; + + bio::format_input_handler input_handler{istream}; + + bio::record, + seqan3::type_list> + rec; + + for (unsigned i = 0; i < 3; ++i) + { + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), ids[i]); + if constexpr (std::same_as, char>) + { + EXPECT_RANGE_EQ(rec.seq() | seqan3::views::char_strictly_to, seqs[i]); + } + else + { + EXPECT_RANGE_EQ(rec.seq(), seqs[i]); + } + if constexpr (std::same_as, char>) + { + EXPECT_RANGE_EQ(rec.qual() | seqan3::views::char_strictly_to, quals[i]); + } + else + { + EXPECT_RANGE_EQ(rec.qual(), quals[i]); + } + } + } + + void do_read_test(std::string const & input) + { + /* containers */ + do_read_test_impl(input); + do_read_test_impl, std::vector>(input); + + /* views */ + do_read_test_impl(input); + do_read_test_impl), + decltype(std::string_view{} | seqan3::views::char_strictly_to)>(input); + } +}; + +// ---------------------------------------------------------------------------- +// simple tests +// ---------------------------------------------------------------------------- + +TEST_F(read, simple) +{ + do_read_test(default_input); +} + +TEST_F(read, no_trailing_newline) +{ + std::string input = + R"raw(@ID1 +ACGTTTTTTTTTTTTTTT ++ +!##$%&'()*+,-./++- +@ID2 +ACGTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT ++ +!##$&'()*+,-./+)*+,-)*+,-)*+,-)*+,BDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDE +@ID3 lala +ACGTTTA ++ +!!!!!!!)raw"; + + do_read_test(input); +} + +TEST_F(read, double_id) +{ + std::string input = + R"raw(@ID1 +ACGTTTTTTTTTTTTTTT ++ID1 +!##$%&'()*+,-./++- +@ID2 +ACGTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT ++ID2 +!##$&'()*+,-./+)*+,-)*+,-)*+,-)*+,BDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDEBDE +@ID3 lala +ACGTTTA ++ID3 lala +!!!!!!!)raw"; + + do_read_test(input); +} + +// ---------------------------------------------------------------------------- +// custom tests +// ---------------------------------------------------------------------------- + +TEST_F(read, empty_seq) +{ + std::string const input = + R"raw(@ID1 + ++ + +@ID2 + ++ + +@ID3 lala + ++ + +)raw"; + + std::istringstream istream{input}; + bio::format_input_handler input_handler{istream}; + default_rec_t rec; + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID1"sv); + EXPECT_TRUE(std::ranges::empty(rec.seq())); + EXPECT_TRUE(std::ranges::empty(rec.qual())); + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID2"sv); + EXPECT_TRUE(std::ranges::empty(rec.seq())); + EXPECT_TRUE(std::ranges::empty(rec.qual())); + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID3 lala"sv); + EXPECT_TRUE(std::ranges::empty(rec.seq())); + EXPECT_TRUE(std::ranges::empty(rec.qual())); +} + +struct options_t +{ + bool truncate_ids = false; +}; + +TEST_F(read, truncate_ids_off) +{ + std::istringstream istream{default_input}; + bio::format_input_handler input_handler{istream, options_t{}}; + default_rec_t rec; + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID1"sv); + EXPECT_RANGE_EQ(rec.seq(), seqs[0]); + EXPECT_RANGE_EQ(rec.qual(), quals[0]); + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID2"sv); + EXPECT_RANGE_EQ(rec.seq(), seqs[1]); + EXPECT_RANGE_EQ(rec.qual(), quals[1]); + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID3 lala"sv); + EXPECT_RANGE_EQ(rec.seq(), seqs[2]); + EXPECT_RANGE_EQ(rec.qual(), quals[2]); +} + +TEST_F(read, truncate_ids_on) +{ + std::istringstream istream{default_input}; + bio::format_input_handler input_handler{istream, options_t{.truncate_ids = true}}; + default_rec_t rec; + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID1"sv); + EXPECT_RANGE_EQ(rec.seq(), seqs[0]); + EXPECT_RANGE_EQ(rec.qual(), quals[0]); + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID2"sv); + EXPECT_RANGE_EQ(rec.seq(), seqs[1]); + EXPECT_RANGE_EQ(rec.qual(), quals[1]); + + input_handler.parse_next_record_into(rec); + EXPECT_RANGE_EQ(rec.id(), "ID3"sv); + EXPECT_RANGE_EQ(rec.seq(), seqs[2]); + EXPECT_RANGE_EQ(rec.qual(), quals[2]); +} + +// ---------------------------------------------------------------------------- +// failure +// ---------------------------------------------------------------------------- + +TEST_F(read, fail_no_input) +{ + std::string const input{}; + + std::istringstream istream{input}; + EXPECT_THROW(bio::format_input_handler{istream}, bio::file_open_error); +} + +TEST_F(read, fail_no_id) +{ + std::string const input{"foo\nACGT"}; + + std::istringstream istream{input}; + bio::format_input_handler input_handler{istream}; + default_rec_t rec; + + EXPECT_THROW(input_handler.parse_next_record_into(rec), bio::parse_error); +} + +TEST_F(read, fail_no_plus) +{ + std::string const input{"@foo\nACGT\nbar"}; + + std::istringstream istream{input}; + bio::format_input_handler input_handler{istream}; + default_rec_t rec; + + EXPECT_THROW(input_handler.parse_next_record_into(rec), bio::parse_error); +} + +TEST_F(read, fail_size_mismatch) +{ + std::string const input = + R"raw(@ID1 +ACGTTTTTTTTTTTTTTT ++ID1 +!##$%&'()*+,-./++ +)raw"; + + std::istringstream istream{input}; + bio::format_input_handler input_handler{istream}; + default_rec_t rec; + + EXPECT_THROW(input_handler.parse_next_record_into(rec), bio::parse_error); +} + +TEST_F(read, fail_illegal_alphabet) +{ + std::string input{"@foo\nFOOBAR\n+\n!!!!!!\n"}; + + std::istringstream istream{input}; + bio::format_input_handler input_handler{istream}; + using rec_t = bio::record, + seqan3::type_list>>; + + rec_t rec; + + EXPECT_THROW(input_handler.parse_next_record_into(rec), seqan3::invalid_char_assignment); +} diff --git a/test/unit/seq_io/seq_io_reader_test.cpp b/test/unit/seq_io/seq_io_reader_test.cpp index bd0c916..513330d 100644 --- a/test/unit/seq_io/seq_io_reader_test.cpp +++ b/test/unit/seq_io/seq_io_reader_test.cpp @@ -68,11 +68,11 @@ TEST(seq_io_reader, constructor1_just_filename) TEST(seq_io_reader, constructor1_with_opts) { bio::seq_io::reader_options opt{.field_types = bio::seq_io::field_types_protein}; + seq_io_reader_filename_constructor(true, std::move(opt)); + using control_t = bio::seq_io::reader, std::remove_cvref_t, - seqan3::type_list>; - - seq_io_reader_filename_constructor(true, std::move(opt)); + seqan3::type_list>; EXPECT_TRUE((std::same_as)); } @@ -85,17 +85,17 @@ TEST(seq_io_reader, constructor2_just_filename_direct_format) TEST(seq_io_reader, constructor2_with_opts_direct_format) { bio::seq_io::reader_options opt{.field_types = bio::seq_io::field_types_dna}; + seq_io_reader_filename_constructor(false, bio::fasta{}, std::move(opt)); + using control_t = bio::seq_io::reader, std::remove_cvref_t, - seqan3::type_list>; - - seq_io_reader_filename_constructor(false, bio::fasta{}, std::move(opt)); + seqan3::type_list>; EXPECT_TRUE((std::same_as)); } TEST(seq_io_reader, constructor2_just_filename_format_variant) { - std::variant var{}; + bio::seq_io::reader<>::format_type var{}; seq_io_reader_filename_constructor(false, var); EXPECT_TRUE((std::same_as>)); @@ -103,13 +103,13 @@ TEST(seq_io_reader, constructor2_just_filename_format_variant) TEST(seq_io_reader, constructor2_with_opts_format_variant) { - std::variant var{}; - bio::seq_io::reader_options opt{.field_types = bio::seq_io::field_types_dna}; + bio::seq_io::reader<>::format_type var{}; + bio::seq_io::reader_options opt{.field_types = bio::seq_io::field_types_dna}; + seq_io_reader_filename_constructor(false, var, std::move(opt)); + using control_t = bio::seq_io::reader, std::remove_cvref_t, - seqan3::type_list>; - - seq_io_reader_filename_constructor(false, var, std::move(opt)); + seqan3::type_list>; EXPECT_TRUE((std::same_as)); } @@ -125,11 +125,11 @@ TEST(seq_io_reader, constructor3_with_opts) { std::istringstream str; bio::seq_io::reader_options opt{.field_types = bio::seq_io::field_types_dna}; + EXPECT_NO_THROW((bio::seq_io::reader{str, bio::fasta{}, opt})); + using control_t = bio::seq_io::reader, std::remove_cvref_t, - seqan3::type_list>; - - EXPECT_NO_THROW((bio::seq_io::reader{str, bio::fasta{}, opt})); + seqan3::type_list>; EXPECT_TRUE((std::same_as)); } @@ -145,11 +145,11 @@ TEST(seq_io_reader, constructor4_with_opts) { std::istringstream str; bio::seq_io::reader_options opt{.field_types = bio::seq_io::field_types_dna}; + EXPECT_NO_THROW((bio::seq_io::reader{std::move(str), bio::fasta{}, opt})); + using control_t = bio::seq_io::reader, std::remove_cvref_t, - seqan3::type_list>; - - EXPECT_NO_THROW((bio::seq_io::reader{std::move(str), bio::fasta{}, opt})); + seqan3::type_list>; EXPECT_TRUE((std::same_as)); }