dev@1430: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
dev@1430: 
dev@1430: /*
dev@1430:     Sonic Visualiser
dev@1430:     An audio file viewer and annotation editor.
dev@1430:     Centre for Digital Music, Queen Mary, University of London.
dev@1438:     This file copyright 2017 Queen Mary, University of London.
dev@1430:     
dev@1430:     This program is free software; you can redistribute it and/or
dev@1430:     modify it under the terms of the GNU General Public License as
dev@1430:     published by the Free Software Foundation; either version 2 of the
dev@1430:     License, or (at your option) any later version.  See the file
dev@1430:     COPYING included with this distribution for more information.
dev@1430: */
dev@1430: 
dev@1430: #ifndef TEST_CSV_STREAM_H
dev@1430: #define TEST_CSV_STREAM_H
dev@1430: 
dev@1434: #include <QtTest>
dev@1430: #include <QObject>
dev@1434: #include <sstream>
dev@1434: #include <functional>
dev@1434: 
dev@1434: #include "base/ProgressReporter.h"
dev@1434: #include "base/DataExportOptions.h"
dev@1446: #include "base/Selection.h"
dev@1449: #include "data/model/NoteModel.h"
dev@1430: #include "../CSVStreamWriter.h"
dev@1434: #include "../../model/test/MockWaveModel.h"
dev@1434: 
dev@1448: class StubReporter : public ProgressReporter
dev@1434: {
dev@1448: public:
dev@1448:     StubReporter( std::function<bool()> isCancelled )
dev@1448:         : m_isCancelled(isCancelled) {}
dev@1448:     bool isDefinite() const override { return true; }
dev@1448:     void setDefinite(bool) override {}
dev@1448:     bool wasCancelled() const override { return m_isCancelled(); }
dev@1448:     void setMessage(QString) override {}
dev@1448:     void setProgress(int p) override
dev@1449:     { 
dev@1448:         ++m_calls;
dev@1448:         m_percentageLog.push_back(p);
dev@1448:     }
dev@1440: 
dev@1448:     size_t getCallCount() const { return m_calls; }
dev@1448:     std::vector<int> getPercentageLog() const { return m_percentageLog; }
dev@1448:     void reset() { m_calls = 0; }
dev@1448: private:
dev@1448:     size_t m_calls = 0;
dev@1448:     std::function<bool()> m_isCancelled;
dev@1448:     std::vector<int> m_percentageLog;
dev@1448: };
dev@1430: 
dev@1430: class CSVStreamWriterTest : public QObject
dev@1430: {
dev@1430:     Q_OBJECT
dev@1430: public:
dev@1434:     std::string getExpectedString()
dev@1434:     {
dev@1434:         return
dev@1434:         {
dev@1434:           "0,0,0\n"
dev@1434:           "1,0,0\n"
dev@1434:           "2,0,0\n"
dev@1434:           "3,0,0\n"
dev@1434:           "4,1,1\n"
dev@1434:           "5,1,1\n"
dev@1434:           "6,1,1\n"
dev@1434:           "7,1,1\n"
dev@1434:           "8,1,1\n"
dev@1434:           "9,1,1\n"
dev@1434:           "10,1,1\n"
dev@1434:           "11,1,1\n"
dev@1434:           "12,1,1\n"
dev@1434:           "13,1,1\n"
dev@1434:           "14,1,1\n"
dev@1434:           "15,1,1\n"
dev@1434:           "16,1,1\n"
dev@1434:           "17,1,1\n"
dev@1434:           "18,1,1\n"
dev@1434:           "19,1,1\n"
dev@1434:           "20,0,0\n"
dev@1434:           "21,0,0\n"
dev@1434:           "22,0,0\n"
dev@1434:           "23,0,0"
dev@1434:         };
dev@1434:     }
dev@1430: 
dev@1430: private slots:
dev@1434:     void simpleValidOutput()
dev@1430:     {
dev@1434:         MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1430: 
dev@1434:         std::ostringstream oss;
dev@1438:         const auto result = CSVStreamWriter::writeInChunks(oss, mwm);
Chris@1833:         
dev@1434:         QVERIFY( oss.str() == getExpectedString() );
dev@1434:         QVERIFY( result );
dev@1434:     }
dev@1434: 
dev@1434:     void callsReporterCorrectTimes()
dev@1434:     {
dev@1434:         MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1434:         StubReporter reporter { []() -> bool { return false; } };
dev@1434:         const auto expected = getExpectedString();
dev@1434: 
dev@1434:         std::ostringstream oss;
dev@1434:         const auto writeStreamWithBlockSize = [&](int blockSize) {
dev@1438:             return CSVStreamWriter::writeInChunks(
dev@1434:                 oss,
dev@1434:                 mwm,
dev@1434:                 &reporter,
dev@1434:                 ",",
dev@1434:                 DataExportDefaults,
dev@1434:                 blockSize
dev@1434:             );
dev@1434:         };
dev@1434: 
dev@1434:         const auto reset = [&]() {
dev@1434:             oss.str({});
dev@1434:             reporter.reset();
dev@1434:         };
dev@1434: 
dev@1434:         const auto nonIntegerMultipleResult = writeStreamWithBlockSize(5);
dev@1434:         QVERIFY( nonIntegerMultipleResult );
dev@1434:         QVERIFY( reporter.getCallCount() == 5 /* 4.8 rounded up */ );
dev@1434:         QVERIFY( oss.str() == expected );
dev@1434:         reset();
dev@1434: 
dev@1434:         const auto integerMultiple = writeStreamWithBlockSize(2);
dev@1434:         QVERIFY( integerMultiple );
dev@1434:         QVERIFY( reporter.getCallCount() == 12 );
dev@1434:         QVERIFY( oss.str() == expected );
dev@1434:         reset();
dev@1434: 
dev@1434:         const auto largerThanNumberOfSamples = writeStreamWithBlockSize(100);
dev@1434:         QVERIFY( largerThanNumberOfSamples );
dev@1434:         QVERIFY( reporter.getCallCount() == 1 );
dev@1434:         QVERIFY( oss.str() == expected );
dev@1434:         reset();
dev@1434: 
dev@1434:         const auto zero = writeStreamWithBlockSize(0);
dev@1434:         QVERIFY( zero == false );
dev@1434:         QVERIFY( reporter.getCallCount() == 0 );
dev@1434:     }
dev@1434: 
dev@1434:     void isCancellable()
dev@1434:     {
dev@1434:         MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1434:         StubReporter reporter { []() -> bool { return true; } };
dev@1434: 
dev@1434:         std::ostringstream oss;
dev@1438:         const auto cancelImmediately = CSVStreamWriter::writeInChunks(
dev@1434:             oss,
dev@1434:             mwm,
dev@1434:             &reporter,
dev@1434:             ",",
dev@1434:             DataExportDefaults,
dev@1434:             4
dev@1434:         );
dev@1434:         QVERIFY( cancelImmediately == false );
dev@1434:         QVERIFY( reporter.getCallCount() == 0 );
dev@1434: 
dev@1434:         StubReporter cancelMidway { 
dev@1434:             [&]() { return cancelMidway.getCallCount() == 3; } 
dev@1434:         };
dev@1438:         const auto cancelledMidway = CSVStreamWriter::writeInChunks(
dev@1434:             oss,
dev@1434:             mwm,
dev@1434:             &cancelMidway,
dev@1434:             ",",
dev@1434:             DataExportDefaults,
dev@1434:             4
dev@1434:         );
dev@1434:         QVERIFY( cancelMidway.getCallCount() == 3 );
dev@1434:         QVERIFY( cancelledMidway == false );
dev@1430:     }
dev@1440: 
dev@1440:     void zeroStartTimeReportsPercentageCorrectly()
dev@1440:     {
dev@1440:         MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1440:         StubReporter reporter { []() -> bool { return false; } };
dev@1440:         std::ostringstream oss;
dev@1440:         const auto succeeded = CSVStreamWriter::writeInChunks(
dev@1440:             oss,
dev@1440:             mwm,
dev@1440:             &reporter,
dev@1440:             ",",
dev@1440:             DataExportDefaults,
dev@1440:             4
dev@1440:         );
dev@1440:         QVERIFY( succeeded == true );
dev@1440:         QVERIFY( reporter.getCallCount() == 6 );
dev@1440:         const std::vector<int> expectedCallLog {
dev@1440:             16,
dev@1440:             33,
dev@1440:             50,
dev@1440:             66,
dev@1440:             83,
dev@1440:             100
dev@1440:         };
dev@1440:         QVERIFY( reporter.getPercentageLog() == expectedCallLog );
dev@1440:         QVERIFY( oss.str() == getExpectedString() );
dev@1440:     }
dev@1440: 
dev@1440:     void nonZeroStartTimeReportsPercentageCorrectly()
dev@1440:     {
dev@1440:         MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1440:         StubReporter reporter { []() -> bool { return false; } };
dev@1440:         std::ostringstream oss;
dev@1440:         const auto writeSubSection = CSVStreamWriter::writeInChunks(
dev@1440:             oss,
dev@1440:             mwm,
dev@1440:             {4, 20},
dev@1440:             &reporter,
dev@1440:             ",",
dev@1440:             DataExportDefaults,
dev@1440:             4
dev@1440:         );
dev@1440:         QVERIFY( reporter.getCallCount() == 4 );
dev@1440:         const std::vector<int> expectedCallLog {
dev@1440:             25,
dev@1440:             50,
dev@1440:             75,
dev@1440:             100
dev@1440:         };
dev@1440:         QVERIFY( reporter.getPercentageLog() == expectedCallLog );
dev@1440:         QVERIFY( writeSubSection == true );
dev@1440:         const std::string expectedOutput {
dev@1440:           "4,1,1\n"
dev@1440:           "5,1,1\n"
dev@1440:           "6,1,1\n"
dev@1440:           "7,1,1\n"
dev@1440:           "8,1,1\n"
dev@1440:           "9,1,1\n"
dev@1440:           "10,1,1\n"
dev@1440:           "11,1,1\n"
dev@1440:           "12,1,1\n"
dev@1440:           "13,1,1\n"
dev@1440:           "14,1,1\n"
dev@1440:           "15,1,1\n"
dev@1440:           "16,1,1\n"
dev@1440:           "17,1,1\n"
dev@1440:           "18,1,1\n"
dev@1440:           "19,1,1"
dev@1440:         };
dev@1440:         QVERIFY( oss.str() == expectedOutput );
dev@1440:     }
dev@1446: 
dev@1446:     void multipleSelectionOutput()
dev@1446:     {
dev@1446:         MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1446:         StubReporter reporter { []() -> bool { return false; } };
dev@1446:         std::ostringstream oss;
dev@1446:         MultiSelection regions;
dev@1446:         regions.addSelection({0, 2});
dev@1446:         regions.addSelection({4, 6});
dev@1446:         regions.addSelection({16, 18});
Chris@1494: //        qDebug("End frame: %lld", (long long int)mwm.getEndFrame());
dev@1446:         const std::string expectedOutput {
dev@1446:           "0,0,0\n"
dev@1446:           "1,0,0\n"
dev@1446:           "4,1,1\n"
dev@1446:           "5,1,1\n"
dev@1446:           "16,1,1\n"
dev@1446:           "17,1,1"
dev@1446:         };
dev@1446:         const auto wroteMultiSection = CSVStreamWriter::writeInChunks(
dev@1446:             oss,
dev@1446:             mwm,
dev@1446:             regions,
dev@1446:             &reporter,
dev@1446:             ",",
dev@1446:             DataExportDefaults,
dev@1446:             2
dev@1446:         );
dev@1446:         QVERIFY( wroteMultiSection == true );
dev@1446:         QVERIFY( reporter.getCallCount() == 3 );
dev@1446:         const std::vector<int> expectedCallLog { 33, 66, 100 };
dev@1446:         QVERIFY( reporter.getPercentageLog() == expectedCallLog );
Chris@1494: //        qDebug("%s", oss.str().c_str());
dev@1446:         QVERIFY( oss.str() == expectedOutput );
dev@1446:     }
dev@1449: 
dev@1449:     void writeSparseModel()
dev@1449:     {
dev@1449:         const auto pentatonicFromRoot = [](float midiPitch) {
dev@1449:             return std::vector<float> {
dev@1449:                 0 + midiPitch,
dev@1449:                 2 + midiPitch,
dev@1449:                 4 + midiPitch,
dev@1449:                 7 + midiPitch,
dev@1449:                 9 + midiPitch
dev@1449:             };
dev@1449:         };
dev@1449:         const auto cMajorPentatonic = pentatonicFromRoot(60.0);
dev@1449:         NoteModel notes(8 /* sampleRate */, 4 /* resolution */);
dev@1449:         sv_frame_t startFrame = 0;
dev@1449:         for (const auto& note : cMajorPentatonic) {
Chris@1644:             notes.add({startFrame, note, 4, 1.f, ""});
dev@1449:             startFrame += 8;
dev@1449:         }
Chris@1494: //        qDebug("Create Expected Output\n");
dev@1449: 
dev@1449:         // NB. removed end line break
Chris@1833:         QString expectedOutput;
Chris@1833:         auto rows = notes.toStringExportRows({}, 0, notes.getEndFrame());
Chris@1833:         for (auto row: rows) {
Chris@1833:             expectedOutput += StringBits::joinDelimited(row, ",") + "\n";
Chris@1833:         }
Chris@1833:         expectedOutput = expectedOutput.trimmed();
dev@1449: 
dev@1449:         StubReporter reporter { []() -> bool { return false; } };
dev@1449:         std::ostringstream oss;
Chris@1494: //        qDebug("End frame: %lld", (long long int)notes.getEndFrame());
Chris@1494: //        qDebug("Write streaming\n");
dev@1449:         const auto wroteSparseModel = CSVStreamWriter::writeInChunks(
dev@1449:             oss,
dev@1449:             notes,
dev@1449:             &reporter,
dev@1449:             ",",
dev@1449:             DataExportDefaults,
dev@1449:             2
dev@1449:         );
dev@1449: 
Chris@1609: //        qDebug("\n->>%s<<-\n", expectedOutput.toLocal8Bit().data());
Chris@1609: //        qDebug("\n->>%s<<-\n", oss.str().c_str());
dev@1449:         QVERIFY( wroteSparseModel == true );
Chris@1679:         QVERIFY( oss.str() != std::string() );
dev@1449:         QVERIFY( oss.str() == expectedOutput.toStdString() );
dev@1449:     }
Chris@1834: 
Chris@1834:     void writeWithQuotingRequired()
Chris@1834:     {
Chris@1834:         QString commaLabel =
Chris@1834:             "This label contains punctuation, specifically, commas";
Chris@1834:         QString quoteSpaceLabel =
Chris@1834:             "This label contains spaces and \"double quotes\"";
Chris@1834:         
Chris@1834:         NoteModel notes(8, 4);
Chris@1834:         notes.add({ 0, 64, 4, 1.f, commaLabel });
Chris@1834:         notes.add({ 16, 64, 6, 1.f, quoteSpaceLabel });
Chris@1834: 
Chris@1834:         QString expectedWithCommaSeparator =
Chris@1834:             QString("0.000000000,64,0.500000000,1,\"") +
Chris@1834:             commaLabel +
Chris@1834:             QString("\"\n") +
Chris@1834:             QString("2.000000000,64,0.750000000,1,") +
Chris@1834:             quoteSpaceLabel;
Chris@1834: 
Chris@1834:         QString expectedWithSpaceSeparator =
Chris@1834:             QString("0.000000000 64 0.500000000 1 \"") +
Chris@1834:             commaLabel +
Chris@1834:             QString("\"\n") +
Chris@1834:             QString("2.000000000 64 0.750000000 1 \"") +
Chris@1834:             QString("This label contains spaces and \"\"double quotes\"\"") +
Chris@1834:             QString("\"");
Chris@1834: 
Chris@1834:         StubReporter reporter { []() -> bool { return false; } };
Chris@1834:         std::ostringstream oss;
Chris@1834:         auto wroteSparseModel = CSVStreamWriter::writeInChunks(
Chris@1834:             oss,
Chris@1834:             notes,
Chris@1834:             &reporter,
Chris@1834:             ",",
Chris@1834:             DataExportDefaults,
Chris@1834:             2
Chris@1834:         );
Chris@1834: 
Chris@1834:         QVERIFY( wroteSparseModel == true );
Chris@1834:         QVERIFY( oss.str() != std::string() );
Chris@1834:         QVERIFY( oss.str() == expectedWithCommaSeparator.toStdString() );
Chris@1834: 
Chris@1834:         std::ostringstream oss2;
Chris@1834:         wroteSparseModel = CSVStreamWriter::writeInChunks(
Chris@1834:             oss2,
Chris@1834:             notes,
Chris@1834:             &reporter,
Chris@1834:             " ",
Chris@1834:             DataExportDefaults,
Chris@1834:             2
Chris@1834:         );
Chris@1834: 
Chris@1834:         QVERIFY( wroteSparseModel == true );
Chris@1834:         QVERIFY( oss2.str() != std::string() );
Chris@1834:         QVERIFY( oss2.str() == expectedWithSpaceSeparator.toStdString() );
Chris@1834:     }
dev@1430: };
dev@1430: 
Chris@1454: #endif