diff --git a/gamgee/fastq.h b/gamgee/fastq.h index 230cf6bd4..a5c8e6a9a 100644 --- a/gamgee/fastq.h +++ b/gamgee/fastq.h @@ -27,19 +27,34 @@ class Fastq { m_name {name}, m_comment {comment}, m_sequence{sequence}, m_quals{quals} {} + Fastq(const Fastq&) = default; + Fastq& operator=(const Fastq&) = default; + Fastq(Fastq&&) = default; + Fastq& operator=(Fastq&&) = default; + /** * @brief inequality comparison of all fields in the record * + * @return true if any field differs (string comparison) + */ + bool operator!=(const Fastq& other) const { + return !(*this == other); + } + + /** + * @brief equality comparison of all fields in the record + * * @return true only if every field is the same (string comparison) */ - bool operator!=(const Fastq& other) { + bool operator==(const Fastq& other) const { return m_name == other.m_name && m_comment == other.m_comment && m_sequence == other.m_sequence && m_quals == other.m_quals; } + std::string name() const { return m_name; } std::string comment() const { return m_comment; } std::string sequence() const { return m_sequence; } diff --git a/gamgee/indexed_sam_reader.h b/gamgee/indexed_sam_reader.h index adb54b16b..f3e0991e3 100644 --- a/gamgee/indexed_sam_reader.h +++ b/gamgee/indexed_sam_reader.h @@ -56,10 +56,14 @@ class IndexedSamReader { IndexedSamReader& operator=(IndexedSamReader&& other) = default; /** - * @brief no copy construction/assignment allowed for iterators and readers + * @brief no copy construction/assignment allowed for input iterators and readers */ IndexedSamReader(const IndexedSamReader& other) = delete; - IndexedSamReader& operator=(const IndexedSamReader& other) = delete; + + /** + * @copydoc IndexedSamReader(IndexedSamReader&) + */ + IndexedSamReader& operator=(IndexedSamReader& other) = delete; /** * @brief creates a ITERATOR pointing at the start of the input stream (needed by for-each diff --git a/test/fastq_reader_test.cpp b/test/fastq_reader_test.cpp index 557950a46..b4fb28428 100644 --- a/test/fastq_reader_test.cpp +++ b/test/fastq_reader_test.cpp @@ -1,7 +1,8 @@ -#include "../gamgee/fastq_reader.h" - #include +#include "fastq_reader.h" +#include "test_utils.h" + using namespace std; using namespace gamgee; @@ -45,3 +46,22 @@ BOOST_AUTO_TEST_CASE( read_fastq_vector_too_large ) { BOOST_CHECK_THROW(vector_too_large("testdata/complete_same_seq.fa"), std::runtime_error); } + +BOOST_AUTO_TEST_CASE( fastq_copy_and_move_constructor ) { + auto it = FastqReader{"testdata/complete_same_seq.fa"}.begin(); + auto c0 = *it; + auto copies = check_copy_constructor(c0); + auto c1 = get<0>(copies); + auto c2 = get<1>(copies); + auto c3 = get<2>(copies); + BOOST_CHECK(c0 == c1); + BOOST_CHECK(c0 == c2); + BOOST_CHECK(c0 == c3); + c1.set_name("modified"); + BOOST_CHECK(c1 != c0); + BOOST_CHECK(c1 != c2); + auto m0 = *it; + auto m1 = check_move_constructor(m0); + auto m2 = *it; + BOOST_CHECK(m1 == m2); +} diff --git a/test/indexed_sam_reader_test.cpp b/test/indexed_sam_reader_test.cpp index 72b17a280..1eb8b1f63 100644 --- a/test/indexed_sam_reader_test.cpp +++ b/test/indexed_sam_reader_test.cpp @@ -1,6 +1,8 @@ +#include + #include "indexed_sam_reader.h" +#include "test_utils.h" -#include using namespace std; using namespace gamgee; @@ -77,12 +79,18 @@ BOOST_AUTO_TEST_CASE( indexed_single_readers_empty ) } BOOST_AUTO_TEST_CASE( indexed_single_readers_move_constructor_and_assignment ) { + auto r0 = IndexedSingleSamReader{"testdata/test_simple.bam", vector{"."}}; + auto m1 = check_move_constructor(r0); + auto m2 = IndexedSingleSamReader{"testdata/test_simple.bam", vector{"."}}; + BOOST_CHECK_EQUAL((*(m1.begin())).alignment_start(), (*(m2.begin())).alignment_start()); +} + +BOOST_AUTO_TEST_CASE( indexed_single_readers_begin_always_restarts ) { auto reader1 = IndexedSingleSamReader{"testdata/test_simple.bam", vector{"."}}; auto it1 = reader1.begin(); - auto reader2 = std::move(reader1); // check move constructor - reader1 = IndexedSingleSamReader{"testdata/test_simple.bam", vector{"."}}; // check move assignment - auto it2 = reader2.begin(); - auto it3 = reader1.begin(); - BOOST_CHECK_EQUAL((*it1).alignment_start(), (*it2).alignment_start()); // unlike the SingleSamReader, these should be the same because they are pointing at the exact same restarted iterator - BOOST_CHECK_EQUAL((*it1).alignment_start(), (*it3).alignment_start()); // these should be the same! both pointing at the first record + ++it1; + auto it2 = reader1.begin(); + BOOST_CHECK_NE((*it1).alignment_start(), (*it2).alignment_start()); + ++it2; + BOOST_CHECK_EQUAL((*it1).alignment_start(), (*it2).alignment_start()); } diff --git a/test/interval_test.cpp b/test/interval_test.cpp index 19a54f3cc..1f013a77b 100644 --- a/test/interval_test.cpp +++ b/test/interval_test.cpp @@ -1,7 +1,8 @@ -#include "interval.h" - #include +#include "interval.h" +#include "test_utils.h" + #include #include #include @@ -119,3 +120,19 @@ BOOST_AUTO_TEST_CASE( interval_equality ) i.set_chr("TAST"); BOOST_CHECK(!(i == j)); } + +BOOST_AUTO_TEST_CASE( interval_copy_and_move_constructors ) { + auto i0 = Interval {"A", 1'000, 2'000}; + auto copies = check_copy_constructor(i0); + auto c2 = get<1>(copies); + BOOST_CHECK(i0 == get<0>(copies)); + BOOST_CHECK(i0 == c2); + BOOST_CHECK(i0 == get<2>(copies)); + c2.set_start(1'500); + BOOST_CHECK(i0 != c2); + BOOST_CHECK(c2 != get<0>(copies)); + BOOST_CHECK(c2 != get<1>(copies)); + BOOST_CHECK(c2 != get<2>(copies)); + auto m1 = check_move_constructor(get<0>(copies)); + BOOST_CHECK(i0 == m1); +} diff --git a/test/sam_header_test.cpp b/test/sam_header_test.cpp index d380d42ea..209f6a525 100644 --- a/test/sam_header_test.cpp +++ b/test/sam_header_test.cpp @@ -1,7 +1,8 @@ -#include "sam_reader.h" - #include +#include "sam_reader.h" +#include "test_utils.h" + using namespace std; using namespace gamgee; @@ -18,11 +19,8 @@ BOOST_AUTO_TEST_CASE( sam_header ) { /** @todo Need a way to modify the header in between these copies/moves to make sure these are working properly! */ BOOST_AUTO_TEST_CASE( sam_header_constructors ) { auto reader = SingleSamReader{"testdata/test_simple.bam"}; - auto header1 = reader.header(); - auto header2 = header1; // copy constructor - auto header3 = std::move(header1); // move constructor - header1 = header2; // copy assignment - const auto header4 = std::move(header1); // transfer header1's memory somewhere else so we can reuse it for move assignment - header1 = std::move(header3); // move assignment - header1 = header1; // self assignment + auto h0 = reader.header(); + auto copies = check_copy_constructor(h0); + auto moves = check_copy_constructor(h0); + // need builder to be able to modify the header and check. At least this test will blow up if something is not functional. } diff --git a/test/sam_test.cpp b/test/sam_test.cpp index 2a71dd98c..279ab1434 100644 --- a/test/sam_test.cpp +++ b/test/sam_test.cpp @@ -4,6 +4,9 @@ #include "sam_reader.h" #include "sam_builder.h" #include "missing.h" + +#include "test_utils.h" + #include using namespace std; @@ -177,25 +180,26 @@ BOOST_AUTO_TEST_CASE( sam_in_place_cigar_modification ) { BOOST_CHECK_EQUAL(read_cigar[0], Cigar::make_cigar_element(30, CigarOperator::I)); } -BOOST_AUTO_TEST_CASE( sam_cigar_copy_and_move_constructors ) { - auto read = *(SingleSamReader{"testdata/test_simple.bam"}.begin()); - auto cigar = read.cigar(); - auto cigar_copy = cigar; - cigar_copy[0] = Cigar::make_cigar_element(3, CigarOperator::M); - BOOST_CHECK(cigar_copy != cigar); // check that modifying the copy doesn't affect the original - auto cigar_move = std::move(cigar); // move construct a new cigar - cigar_move[0] = Cigar::make_cigar_element(1, CigarOperator::D); - BOOST_CHECK(cigar_move != cigar_copy); // check that modifying the moved one doesn't affect the copy - cigar = cigar_copy; // check the copy assignment now that cigar has been moved to move_cigar - BOOST_CHECK(cigar == cigar_copy); // check that the cigar is now the same as the cigar_copy - BOOST_CHECK(cigar != cigar_move); // check that the cigar is not the same as the moved one - cigar[0] = Cigar::make_cigar_element(2, CigarOperator::N); - BOOST_CHECK(cigar != cigar_copy); // check that modifying the copied version doesn't affect the original - auto cigar_tmp = std::move(cigar); // take ownership of cigar's memory so we can play around with cigar again - cigar = std::move(cigar_move); // we should be back to the original again! - BOOST_CHECK(cigar == read.cigar()); - cigar = cigar; // check self assignment - BOOST_CHECK(cigar == read.cigar()); +BOOST_AUTO_TEST_CASE( sam_cigar_templated_copy_and_move_constructors ) { + auto it = SingleSamReader{"testdata/test_simple.bam"}.begin(); + const auto c0 = (*it).cigar(); + const auto copies = check_copy_constructor(c0); + auto c1 = get<0>(copies); + auto c2 = get<1>(copies); + auto c3 = get<2>(copies); + BOOST_CHECK(c0 == c1); + BOOST_CHECK(c0 == c2); + BOOST_CHECK(c0 == c3); + c1[0] = Cigar::make_cigar_element(30, CigarOperator::I); + BOOST_CHECK(c0 != c1); + auto m0 = (*it).cigar(); + auto m1 = check_move_constructor(m0); + auto m2 = (*it).cigar(); + BOOST_CHECK(m1 == m2); + m1[0] = Cigar::make_cigar_element(20, CigarOperator::I); + BOOST_CHECK(m1 == m2); // the underlying object is the same and it still exists + BOOST_CHECK(m0 == m1); // the underlying object is the same and it still exists (hasn't been destroyed, so they must still match) + BOOST_CHECK(m1 != c1); // check that modifying the moved doesn't affect the copied } BOOST_AUTO_TEST_CASE( invalid_cigar_access ) { @@ -217,25 +221,26 @@ BOOST_AUTO_TEST_CASE( comparing_different_cigars ) { BOOST_CHECK(read1.cigar() != read2.cigar()); } -BOOST_AUTO_TEST_CASE( sam_base_quals_copy_and_move_constructors ) { - auto read = *(SingleSamReader{"testdata/test_simple.bam"}.begin()); - auto bq = read.base_quals(); - auto bq_copy = bq; - bq_copy[0] = 99; - BOOST_CHECK(bq_copy != bq); // check that modifying the copy doesn't affect the original - auto bq_move = std::move(bq); // move construct a new bq - bq_move[0] = 90; - BOOST_CHECK(bq_move != bq_copy); // check that modifying the moved one doesn't affect the copy - bq = bq_copy; // check the copy assignment now that bq has been moved to move_bq - BOOST_CHECK(bq == bq_copy); // check that the bq is now the same as the bq_copy - BOOST_CHECK(bq != bq_move); // check that the bq is not the same as the moved one - bq[0] = 93; - BOOST_CHECK(bq != bq_copy); // check that modifying the copied version doesn't affect the original - auto bq_tmp = std::move(bq); // take ownership of bq's memory so we can play around with bq again - bq = std::move(bq_move); // we should be back to the original again! - BOOST_CHECK(bq == read.base_quals()); - bq = bq; // check self assignment - BOOST_CHECK(bq == read.base_quals()); +BOOST_AUTO_TEST_CASE( sam_base_quals_templated_copy_and_move_constructors ) { + auto it = SingleSamReader{"testdata/test_simple.bam"}.begin(); + auto c0 = (*it).base_quals(); + auto copies = check_copy_constructor(c0); + auto c1 = get<0>(copies); + auto c2 = get<1>(copies); + auto c3 = get<2>(copies); + BOOST_CHECK(c0 == c1); + BOOST_CHECK(c0 == c2); + BOOST_CHECK(c0 == c3); + c1[0] = 99; + BOOST_CHECK(c0 != c1); + auto m0 = (*it).base_quals(); + auto m1 = check_move_constructor(m0); + auto m2 = (*it).base_quals(); + BOOST_CHECK(m1 == m2); + m1[0] = 90; + BOOST_CHECK(m1 == m2); // the underlying object is the same and it still exists + BOOST_CHECK(m0 == m1); // the underlying object is the same and it still exists (hasn't been destroyed, so they must still match) + BOOST_CHECK(m1 != c2); // check that modifying the moved doesn't affect the copied } BOOST_AUTO_TEST_CASE( comparing_different_base_quals ) { @@ -247,25 +252,26 @@ BOOST_AUTO_TEST_CASE( comparing_different_base_quals ) { BOOST_CHECK(read1.base_quals() != read2.base_quals()); } -BOOST_AUTO_TEST_CASE( sam_read_bases_copy_and_move_constructors ) { - auto read = *(SingleSamReader{"testdata/test_simple.bam"}.begin()); - auto bases = read.bases(); - auto bases_copy = bases; - bases_copy.set_base(0, Base::C); - BOOST_CHECK(bases_copy != bases); // check that modifying the copy doesn't affect the original - auto bases_move = std::move(bases); // move construct a new bases - bases_move.set_base(0, Base::N); - BOOST_CHECK(bases_move != bases_copy); // check that modifying the moved one doesn't affect the copy - bases = bases_copy; // check the copy assignment now that bases has been moved to move_bases - BOOST_CHECK(bases == bases_copy); // check that the bases is now the same as the bases_copy - BOOST_CHECK(bases != bases_move); // check that the bases is not the same as the moved one - bases.set_base(0, Base::T); - BOOST_CHECK(bases != bases_copy); // check that modifying the copied version doesn't affect the original - auto bases_tmp = std::move(bases); // take ownership of bases's memory so we can play around with bases again - bases = std::move(bases_move); // we should be back to the original again! - BOOST_CHECK(bases == read.bases()); - bases = bases; // check self assignment - BOOST_CHECK(bases == read.bases()); +BOOST_AUTO_TEST_CASE( sam_read_bases_templated_copy_and_move_constructors ) { + auto it = SingleSamReader{"testdata/test_simple.bam"}.begin(); + auto c0 = (*it).bases(); + auto copies = check_copy_constructor(c0); + auto c1 = get<0>(copies); + auto c2 = get<1>(copies); + auto c3 = get<2>(copies); + BOOST_CHECK(c0 == c1); + BOOST_CHECK(c0 == c2); + BOOST_CHECK(c0 == c3); + c1.set_base(0, Base::C); + BOOST_CHECK(c0 != c1); + auto m0 = (*it).bases(); + auto m1 = check_move_constructor(m0); + auto m2 = (*it).bases(); + BOOST_CHECK(m1 == m2); + m1.set_base(0, Base::N); + BOOST_CHECK(m1 == m2); // the underlying object is the same and it still exists + BOOST_CHECK(m0 == m1); // the underlying object is the same and it still exists (hasn't been destroyed, so they must still match) + BOOST_CHECK(m1 != c2); // check that modifying the moved doesn't affect the copied } BOOST_AUTO_TEST_CASE( comparing_different_read_bases ) { @@ -361,19 +367,26 @@ BOOST_AUTO_TEST_CASE( sam_read_tags ) { BOOST_CHECK(missing(not_a_string_tag)); // this should yield "not a char" which is equal to a missing value } -BOOST_AUTO_TEST_CASE( sam_copy_constructor ) { - const auto read1 = *(SingleSamReader{"testdata/test_simple.bam"}.begin()); - auto read2 = read1; // copy read1 - read2.set_alignment_start(5000); - BOOST_CHECK(read1.alignment_start() != read2.alignment_start()); - read2 = read1; // copy assignment test - BOOST_CHECK_EQUAL(read1.alignment_start(), read2.alignment_start()); - read2.set_alignment_start(1); - read2 = read2; // check self assignment - BOOST_CHECK_EQUAL(read2.alignment_start(), 1); - auto read3 = read1; // check that variable length data field modifications don't affect the original record - read3.base_quals()[0] = 90; - BOOST_CHECK(read1.base_quals()[0] != read3.base_quals()[0]); +BOOST_AUTO_TEST_CASE( sam_templated_copy_and_move_constructors ) { + auto it = SingleSamReader{"testdata/test_simple.bam"}.begin(); + auto c0 = *it; + auto copies = check_copy_constructor(c0); + auto c1 = get<0>(copies); + auto c2 = get<1>(copies); + auto c3 = get<2>(copies); + BOOST_CHECK_EQUAL(c0.alignment_start(), c1.alignment_start()); + BOOST_CHECK_EQUAL(c0.alignment_start(), c2.alignment_start()); + BOOST_CHECK_EQUAL(c0.alignment_start(), c3.alignment_start()); + c1.set_alignment_start(1); + BOOST_CHECK_NE(c0.alignment_start(), c1.alignment_start()); + auto m0 = *it; + auto m1 = check_move_constructor(m0); + BOOST_CHECK(m0.empty()); + auto m2 = *it; + BOOST_CHECK_EQUAL(m1.alignment_start(), m2.alignment_start()); + m1.set_alignment_start(5000); + BOOST_CHECK_NE(m1.alignment_start(), m2.alignment_start()); // the underlying object is the same and it still exists + BOOST_CHECK_NE(m1.alignment_start(), c2.alignment_start()); // check that modifying the moved doesn't affect the copied } void check_read_alignment_starts_and_stops(const Sam& read, const uint32_t astart, const uint32_t astop, const uint32_t ustart, const uint32_t ustop) { diff --git a/test/test_utils.h b/test/test_utils.h new file mode 100644 index 000000000..ea7f0f042 --- /dev/null +++ b/test/test_utils.h @@ -0,0 +1,53 @@ +#ifndef gamgee_test_utils__guard +#define gamgee_test_utils__guard + +#include + +/** + * @brief test code for copy construction and copy assignment for any copy enabled object + * + * This tests copy construction, copy assignment and checks for self copy + * assignment. The starting object is copied to three different objects using + * all the above procedures. It returns a tuple with all three copied objects + * for you to verify that the copy occurred and that it didn't affect the + * original object. This is extremely useful when writing tests for a new class. + * + * @tparam T any class that is copy constructible and copy assignable + * @param original a simple object of class T to make the copies from + * @return a tuple with all three copied objects + */ +template +std::tuple check_copy_constructor (T& original) { + auto obj2 = original; // copy construction + auto obj3 = original; // copy construction + obj2 = obj2; // check self copy-assignment + obj3 = obj2; // copy assignment + return std::make_tuple(original, obj2, obj3); +} + + /** + * @brief test code for move construction and move assignment for any move enabled object + * + * This tests move construction, move assignment and checks for self move + * assignment. The starting object is moved through three different objects using + * all the above procedures. It returns the final moved to object + * for you to verify that the move occurred and that it still matches the + * original object. This is extremely useful when writing tests for a new class. + * + * @warning the original object passed in will be forcefully moved from, therefore in unusable state. + * + * @tparam T any class that is move constructible and move assignable + * @param original a simple object of class T to move from (it will be destroyed) + * @return the last moved to object + */ +template +T check_move_constructor (T& original) { + auto obj2 = std::move(original); // move construction + auto obj3 = std::move(obj2); // create a new object (we can't rely on default construction) + obj2 = std::move(obj3); // check move-assignment + return obj2; +} + + + +#endif diff --git a/test/variant_header_test.cpp b/test/variant_header_test.cpp index c6eea416b..df0d6335a 100644 --- a/test/variant_header_test.cpp +++ b/test/variant_header_test.cpp @@ -1,4 +1,5 @@ #include +#include "test_utils.h" #include "variant_header_builder.h" #include "missing.h" @@ -52,3 +53,17 @@ BOOST_AUTO_TEST_CASE( variant_header_builder_simple_building ) { BOOST_CHECK_EQUAL(vh.field_index("PASS"), 0); } +BOOST_AUTO_TEST_CASE( variant_header_move_and_copy_constructor ) { + auto builder = VariantHeaderBuilder{}; + builder.add_sample("S1"); + auto h0 = builder.build(); + auto copies = check_copy_constructor(h0); + auto c2 = get<2>(copies); + BOOST_CHECK_EQUAL_COLLECTIONS(h0.samples().begin(), h0.samples().end(), get<0>(copies).samples().begin(), get<0>(copies).samples().end()); + BOOST_CHECK_EQUAL_COLLECTIONS(h0.samples().begin(), h0.samples().end(), get<1>(copies).samples().begin(), get<1>(copies).samples().end()); + BOOST_CHECK_EQUAL_COLLECTIONS(h0.samples().begin(), h0.samples().end(), c2.samples().begin(), c2.samples().end()); + auto m1 = check_move_constructor(get<1>(copies)); + BOOST_CHECK_EQUAL_COLLECTIONS(h0.samples().begin(), h0.samples().end(), m1.samples().begin(), m1.samples().end()); + // can't modify a variant header... so this is it for the test. +} + diff --git a/test/variant_reader_test.cpp b/test/variant_reader_test.cpp index db30394fe..8117f7c4c 100644 --- a/test/variant_reader_test.cpp +++ b/test/variant_reader_test.cpp @@ -5,6 +5,7 @@ #include "indexed_variant_reader.h" #include "indexed_variant_iterator.h" #include "missing.h" +#include "test_utils.h" #include @@ -478,31 +479,6 @@ BOOST_AUTO_TEST_CASE( single_variant_reader_vector_too_large ) BOOST_CHECK_THROW((SingleVariantReader{vector{"testdata/test_variants.vcf", "testdata/test_variants.vcf"}}), std::runtime_error); } -BOOST_AUTO_TEST_CASE( single_variant_reader_move_test ) { - auto reader1 = SingleVariantReader{"testdata/test_variants.vcf"}; - - // move construct - auto reader2 = std::move(reader1); - - // move assign - auto reader3 = SingleVariantReader{"testdata/test_variants_missing_data.vcf"}; // different file - reader3 = std::move(reader2); - - auto truth_index = 0u; - for (const auto& record : reader3) { - check_variant_basic_api(record, truth_index); - check_quals_api(record, truth_index); - check_alt_api(record, truth_index); - check_filters_api(record, truth_index); - check_genotype_quals_api(record,truth_index); - check_phred_likelihoods_api(record, truth_index); - check_individual_field_api(record, truth_index); - check_shared_field_api(record, truth_index); - check_genotype_api(record, truth_index); - ++truth_index; - } -} - BOOST_AUTO_TEST_CASE( variant_iterator_move_test ) { auto reader = SingleVariantReader{"testdata/test_variants.vcf"}; auto iter1 = reader.begin(); @@ -808,3 +784,10 @@ BOOST_AUTO_TEST_CASE( indexed_variant_iterator_move_test ) { BOOST_CHECK_EQUAL(rec1.alignment_start(), rec3.alignment_start()); } } + +BOOST_AUTO_TEST_CASE( variant_reader_move_constructor ) { + auto r0 = SingleVariantReader{"testdata/test_variants.bcf"}; + auto r1 = SingleVariantReader{"testdata/test_variants.bcf"}; + auto m1 = check_move_constructor(r1); + BOOST_CHECK_EQUAL(r0.begin().operator*().alignment_start(), m1.begin().operator*().alignment_start()); +}