annotate data/fileio/test/CSVStreamWriterTest.h @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents 0d89abd631ac
children 735b0ccd3f4a
rev   line source
dev@1430 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
dev@1430 2
dev@1430 3 /*
dev@1430 4 Sonic Visualiser
dev@1430 5 An audio file viewer and annotation editor.
dev@1430 6 Centre for Digital Music, Queen Mary, University of London.
dev@1438 7 This file copyright 2017 Queen Mary, University of London.
dev@1430 8
dev@1430 9 This program is free software; you can redistribute it and/or
dev@1430 10 modify it under the terms of the GNU General Public License as
dev@1430 11 published by the Free Software Foundation; either version 2 of the
dev@1430 12 License, or (at your option) any later version. See the file
dev@1430 13 COPYING included with this distribution for more information.
dev@1430 14 */
dev@1430 15
dev@1430 16 #ifndef TEST_CSV_STREAM_H
dev@1430 17 #define TEST_CSV_STREAM_H
dev@1430 18
dev@1434 19 #include <QtTest>
dev@1430 20 #include <QObject>
dev@1434 21 #include <sstream>
dev@1434 22 #include <functional>
dev@1434 23
dev@1434 24 #include "base/ProgressReporter.h"
dev@1434 25 #include "base/DataExportOptions.h"
dev@1446 26 #include "base/Selection.h"
dev@1449 27 #include "data/model/NoteModel.h"
dev@1430 28 #include "../CSVStreamWriter.h"
dev@1434 29 #include "../../model/test/MockWaveModel.h"
dev@1434 30
dev@1448 31 class StubReporter : public ProgressReporter
dev@1434 32 {
dev@1448 33 public:
dev@1448 34 StubReporter( std::function<bool()> isCancelled )
dev@1448 35 : m_isCancelled(isCancelled) {}
dev@1448 36 bool isDefinite() const override { return true; }
dev@1448 37 void setDefinite(bool) override {}
dev@1448 38 bool wasCancelled() const override { return m_isCancelled(); }
dev@1448 39 void setMessage(QString) override {}
dev@1448 40 void setProgress(int p) override
dev@1449 41 {
dev@1448 42 ++m_calls;
dev@1448 43 m_percentageLog.push_back(p);
dev@1448 44 }
dev@1440 45
dev@1448 46 size_t getCallCount() const { return m_calls; }
dev@1448 47 std::vector<int> getPercentageLog() const { return m_percentageLog; }
dev@1448 48 void reset() { m_calls = 0; }
dev@1448 49 private:
dev@1448 50 size_t m_calls = 0;
dev@1448 51 std::function<bool()> m_isCancelled;
dev@1448 52 std::vector<int> m_percentageLog;
dev@1448 53 };
dev@1430 54
dev@1430 55 class CSVStreamWriterTest : public QObject
dev@1430 56 {
dev@1430 57 Q_OBJECT
dev@1430 58 public:
dev@1434 59 std::string getExpectedString()
dev@1434 60 {
dev@1434 61 return
dev@1434 62 {
dev@1434 63 "0,0,0\n"
dev@1434 64 "1,0,0\n"
dev@1434 65 "2,0,0\n"
dev@1434 66 "3,0,0\n"
dev@1434 67 "4,1,1\n"
dev@1434 68 "5,1,1\n"
dev@1434 69 "6,1,1\n"
dev@1434 70 "7,1,1\n"
dev@1434 71 "8,1,1\n"
dev@1434 72 "9,1,1\n"
dev@1434 73 "10,1,1\n"
dev@1434 74 "11,1,1\n"
dev@1434 75 "12,1,1\n"
dev@1434 76 "13,1,1\n"
dev@1434 77 "14,1,1\n"
dev@1434 78 "15,1,1\n"
dev@1434 79 "16,1,1\n"
dev@1434 80 "17,1,1\n"
dev@1434 81 "18,1,1\n"
dev@1434 82 "19,1,1\n"
dev@1434 83 "20,0,0\n"
dev@1434 84 "21,0,0\n"
dev@1434 85 "22,0,0\n"
dev@1434 86 "23,0,0"
dev@1434 87 };
dev@1434 88 }
dev@1430 89
dev@1430 90 private slots:
dev@1434 91 void simpleValidOutput()
dev@1430 92 {
dev@1434 93 MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1430 94
dev@1434 95 std::ostringstream oss;
dev@1438 96 const auto result = CSVStreamWriter::writeInChunks(oss, mwm);
Chris@1833 97
dev@1434 98 QVERIFY( oss.str() == getExpectedString() );
dev@1434 99 QVERIFY( result );
dev@1434 100 }
dev@1434 101
dev@1434 102 void callsReporterCorrectTimes()
dev@1434 103 {
dev@1434 104 MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1434 105 StubReporter reporter { []() -> bool { return false; } };
dev@1434 106 const auto expected = getExpectedString();
dev@1434 107
dev@1434 108 std::ostringstream oss;
dev@1434 109 const auto writeStreamWithBlockSize = [&](int blockSize) {
dev@1438 110 return CSVStreamWriter::writeInChunks(
dev@1434 111 oss,
dev@1434 112 mwm,
dev@1434 113 &reporter,
dev@1434 114 ",",
dev@1434 115 DataExportDefaults,
dev@1434 116 blockSize
dev@1434 117 );
dev@1434 118 };
dev@1434 119
dev@1434 120 const auto reset = [&]() {
dev@1434 121 oss.str({});
dev@1434 122 reporter.reset();
dev@1434 123 };
dev@1434 124
dev@1434 125 const auto nonIntegerMultipleResult = writeStreamWithBlockSize(5);
dev@1434 126 QVERIFY( nonIntegerMultipleResult );
dev@1434 127 QVERIFY( reporter.getCallCount() == 5 /* 4.8 rounded up */ );
dev@1434 128 QVERIFY( oss.str() == expected );
dev@1434 129 reset();
dev@1434 130
dev@1434 131 const auto integerMultiple = writeStreamWithBlockSize(2);
dev@1434 132 QVERIFY( integerMultiple );
dev@1434 133 QVERIFY( reporter.getCallCount() == 12 );
dev@1434 134 QVERIFY( oss.str() == expected );
dev@1434 135 reset();
dev@1434 136
dev@1434 137 const auto largerThanNumberOfSamples = writeStreamWithBlockSize(100);
dev@1434 138 QVERIFY( largerThanNumberOfSamples );
dev@1434 139 QVERIFY( reporter.getCallCount() == 1 );
dev@1434 140 QVERIFY( oss.str() == expected );
dev@1434 141 reset();
dev@1434 142
dev@1434 143 const auto zero = writeStreamWithBlockSize(0);
dev@1434 144 QVERIFY( zero == false );
dev@1434 145 QVERIFY( reporter.getCallCount() == 0 );
dev@1434 146 }
dev@1434 147
dev@1434 148 void isCancellable()
dev@1434 149 {
dev@1434 150 MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1434 151 StubReporter reporter { []() -> bool { return true; } };
dev@1434 152
dev@1434 153 std::ostringstream oss;
dev@1438 154 const auto cancelImmediately = CSVStreamWriter::writeInChunks(
dev@1434 155 oss,
dev@1434 156 mwm,
dev@1434 157 &reporter,
dev@1434 158 ",",
dev@1434 159 DataExportDefaults,
dev@1434 160 4
dev@1434 161 );
dev@1434 162 QVERIFY( cancelImmediately == false );
dev@1434 163 QVERIFY( reporter.getCallCount() == 0 );
dev@1434 164
dev@1434 165 StubReporter cancelMidway {
dev@1434 166 [&]() { return cancelMidway.getCallCount() == 3; }
dev@1434 167 };
dev@1438 168 const auto cancelledMidway = CSVStreamWriter::writeInChunks(
dev@1434 169 oss,
dev@1434 170 mwm,
dev@1434 171 &cancelMidway,
dev@1434 172 ",",
dev@1434 173 DataExportDefaults,
dev@1434 174 4
dev@1434 175 );
dev@1434 176 QVERIFY( cancelMidway.getCallCount() == 3 );
dev@1434 177 QVERIFY( cancelledMidway == false );
dev@1430 178 }
dev@1440 179
dev@1440 180 void zeroStartTimeReportsPercentageCorrectly()
dev@1440 181 {
dev@1440 182 MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1440 183 StubReporter reporter { []() -> bool { return false; } };
dev@1440 184 std::ostringstream oss;
dev@1440 185 const auto succeeded = CSVStreamWriter::writeInChunks(
dev@1440 186 oss,
dev@1440 187 mwm,
dev@1440 188 &reporter,
dev@1440 189 ",",
dev@1440 190 DataExportDefaults,
dev@1440 191 4
dev@1440 192 );
dev@1440 193 QVERIFY( succeeded == true );
dev@1440 194 QVERIFY( reporter.getCallCount() == 6 );
dev@1440 195 const std::vector<int> expectedCallLog {
dev@1440 196 16,
dev@1440 197 33,
dev@1440 198 50,
dev@1440 199 66,
dev@1440 200 83,
dev@1440 201 100
dev@1440 202 };
dev@1440 203 QVERIFY( reporter.getPercentageLog() == expectedCallLog );
dev@1440 204 QVERIFY( oss.str() == getExpectedString() );
dev@1440 205 }
dev@1440 206
dev@1440 207 void nonZeroStartTimeReportsPercentageCorrectly()
dev@1440 208 {
dev@1440 209 MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1440 210 StubReporter reporter { []() -> bool { return false; } };
dev@1440 211 std::ostringstream oss;
dev@1440 212 const auto writeSubSection = CSVStreamWriter::writeInChunks(
dev@1440 213 oss,
dev@1440 214 mwm,
dev@1440 215 {4, 20},
dev@1440 216 &reporter,
dev@1440 217 ",",
dev@1440 218 DataExportDefaults,
dev@1440 219 4
dev@1440 220 );
dev@1440 221 QVERIFY( reporter.getCallCount() == 4 );
dev@1440 222 const std::vector<int> expectedCallLog {
dev@1440 223 25,
dev@1440 224 50,
dev@1440 225 75,
dev@1440 226 100
dev@1440 227 };
dev@1440 228 QVERIFY( reporter.getPercentageLog() == expectedCallLog );
dev@1440 229 QVERIFY( writeSubSection == true );
dev@1440 230 const std::string expectedOutput {
dev@1440 231 "4,1,1\n"
dev@1440 232 "5,1,1\n"
dev@1440 233 "6,1,1\n"
dev@1440 234 "7,1,1\n"
dev@1440 235 "8,1,1\n"
dev@1440 236 "9,1,1\n"
dev@1440 237 "10,1,1\n"
dev@1440 238 "11,1,1\n"
dev@1440 239 "12,1,1\n"
dev@1440 240 "13,1,1\n"
dev@1440 241 "14,1,1\n"
dev@1440 242 "15,1,1\n"
dev@1440 243 "16,1,1\n"
dev@1440 244 "17,1,1\n"
dev@1440 245 "18,1,1\n"
dev@1440 246 "19,1,1"
dev@1440 247 };
dev@1440 248 QVERIFY( oss.str() == expectedOutput );
dev@1440 249 }
dev@1446 250
dev@1446 251 void multipleSelectionOutput()
dev@1446 252 {
dev@1446 253 MockWaveModel mwm({ DC, DC }, 16, 4);
dev@1446 254 StubReporter reporter { []() -> bool { return false; } };
dev@1446 255 std::ostringstream oss;
dev@1446 256 MultiSelection regions;
dev@1446 257 regions.addSelection({0, 2});
dev@1446 258 regions.addSelection({4, 6});
dev@1446 259 regions.addSelection({16, 18});
Chris@1494 260 // qDebug("End frame: %lld", (long long int)mwm.getEndFrame());
dev@1446 261 const std::string expectedOutput {
dev@1446 262 "0,0,0\n"
dev@1446 263 "1,0,0\n"
dev@1446 264 "4,1,1\n"
dev@1446 265 "5,1,1\n"
dev@1446 266 "16,1,1\n"
dev@1446 267 "17,1,1"
dev@1446 268 };
dev@1446 269 const auto wroteMultiSection = CSVStreamWriter::writeInChunks(
dev@1446 270 oss,
dev@1446 271 mwm,
dev@1446 272 regions,
dev@1446 273 &reporter,
dev@1446 274 ",",
dev@1446 275 DataExportDefaults,
dev@1446 276 2
dev@1446 277 );
dev@1446 278 QVERIFY( wroteMultiSection == true );
dev@1446 279 QVERIFY( reporter.getCallCount() == 3 );
dev@1446 280 const std::vector<int> expectedCallLog { 33, 66, 100 };
dev@1446 281 QVERIFY( reporter.getPercentageLog() == expectedCallLog );
Chris@1494 282 // qDebug("%s", oss.str().c_str());
dev@1446 283 QVERIFY( oss.str() == expectedOutput );
dev@1446 284 }
dev@1449 285
dev@1449 286 void writeSparseModel()
dev@1449 287 {
dev@1449 288 const auto pentatonicFromRoot = [](float midiPitch) {
dev@1449 289 return std::vector<float> {
dev@1449 290 0 + midiPitch,
dev@1449 291 2 + midiPitch,
dev@1449 292 4 + midiPitch,
dev@1449 293 7 + midiPitch,
dev@1449 294 9 + midiPitch
dev@1449 295 };
dev@1449 296 };
dev@1449 297 const auto cMajorPentatonic = pentatonicFromRoot(60.0);
dev@1449 298 NoteModel notes(8 /* sampleRate */, 4 /* resolution */);
dev@1449 299 sv_frame_t startFrame = 0;
dev@1449 300 for (const auto& note : cMajorPentatonic) {
Chris@1644 301 notes.add({startFrame, note, 4, 1.f, ""});
dev@1449 302 startFrame += 8;
dev@1449 303 }
Chris@1494 304 // qDebug("Create Expected Output\n");
dev@1449 305
dev@1449 306 // NB. removed end line break
Chris@1833 307 QString expectedOutput;
Chris@1833 308 auto rows = notes.toStringExportRows({}, 0, notes.getEndFrame());
Chris@1833 309 for (auto row: rows) {
Chris@1833 310 expectedOutput += StringBits::joinDelimited(row, ",") + "\n";
Chris@1833 311 }
Chris@1833 312 expectedOutput = expectedOutput.trimmed();
dev@1449 313
dev@1449 314 StubReporter reporter { []() -> bool { return false; } };
dev@1449 315 std::ostringstream oss;
Chris@1494 316 // qDebug("End frame: %lld", (long long int)notes.getEndFrame());
Chris@1494 317 // qDebug("Write streaming\n");
dev@1449 318 const auto wroteSparseModel = CSVStreamWriter::writeInChunks(
dev@1449 319 oss,
dev@1449 320 notes,
dev@1449 321 &reporter,
dev@1449 322 ",",
dev@1449 323 DataExportDefaults,
dev@1449 324 2
dev@1449 325 );
dev@1449 326
Chris@1609 327 // qDebug("\n->>%s<<-\n", expectedOutput.toLocal8Bit().data());
Chris@1609 328 // qDebug("\n->>%s<<-\n", oss.str().c_str());
dev@1449 329 QVERIFY( wroteSparseModel == true );
Chris@1679 330 QVERIFY( oss.str() != std::string() );
dev@1449 331 QVERIFY( oss.str() == expectedOutput.toStdString() );
dev@1449 332 }
dev@1430 333 };
dev@1430 334
Chris@1454 335 #endif