// SPDX-License-Identifier: GPL-2.0 #include "testparse.h" #include "core/device.h" #include "core/dive.h" #include "core/divelog.h" #include "core/divesite.h" #include "core/errorhelper.h" #include "core/trip.h" #include "core/file.h" #include "core/import-csv.h" #include "core/parse.h" #include "core/qthelper.h" #include "core/subsurface-string.h" #include "core/xmlparams.h" #include /* We have to use a macro since QCOMPARE * can only be called from a test method * invoked by the QTest framework */ #define FILE_COMPARE(actual, expected) \ QFile org(expected); \ org.open(QFile::ReadOnly); \ QFile out(actual); \ out.open(QFile::ReadOnly); \ QTextStream orgS(&org); \ QTextStream outS(&out); \ QStringList readin = orgS.readAll().split("\n"); \ QStringList written = outS.readAll().split("\n"); \ while (readin.size() && written.size()) { \ QCOMPARE(written.takeFirst().trimmed(), \ readin.takeFirst().trimmed()); \ } void TestParse::initTestCase() { /* we need to manually tell that the resource exists, because we are using it as library. */ Q_INIT_RESOURCE(subsurface); copy_prefs(&default_prefs, &prefs); } void TestParse::init() { _sqlite3_handle = NULL; } void TestParse::cleanup() { clear_dive_file_data(); // Some test use sqlite3, ensure db is closed sqlite3_close(_sqlite3_handle); } int TestParse::parseCSV(int units, std::string file) { // some basic file parsing tests // // CSV import should work verbose = 1; xml_params params; xml_params_add_int(¶ms, "numberField", 0); xml_params_add_int(¶ms, "dateField", 1); xml_params_add_int(¶ms, "timeField", 2); xml_params_add_int(¶ms, "durationField", 3); xml_params_add_int(¶ms, "locationField", -1); xml_params_add_int(¶ms, "gpsField", -1); xml_params_add_int(¶ms, "maxDepthField", 4); xml_params_add_int(¶ms, "meanDepthField", 5); xml_params_add_int(¶ms, "divemasterField", -1); xml_params_add_int(¶ms, "buddyField", 6); xml_params_add_int(¶ms, "suitField", 7); xml_params_add_int(¶ms, "notesField", -1); xml_params_add_int(¶ms, "weightField", -1); xml_params_add_int(¶ms, "tagsField", -1); xml_params_add_int(¶ms, "separatorIndex", 0); xml_params_add_int(¶ms, "units", units); xml_params_add_int(¶ms, "datefmt", 1); xml_params_add_int(¶ms, "durationfmt", 2); xml_params_add_int(¶ms, "cylindersizeField", -1); xml_params_add_int(¶ms, "startpressureField", -1); xml_params_add_int(¶ms, "endpressureField", -1); xml_params_add_int(¶ms, "o2Field", -1); xml_params_add_int(¶ms, "heField", -1); xml_params_add_int(¶ms, "airtempField", -1); xml_params_add_int(¶ms, "watertempField", -1); return parse_manual_file(file.c_str(), ¶ms, &divelog); } int TestParse::parseDivingLog() { // Parsing of DivingLog import from SQLite database struct dive_site *ds = alloc_or_get_dive_site(0xdeadbeef, *divelog.sites); ds->name = "Suomi - - Hälvälä"; int ret = sqlite3_open(SUBSURFACE_TEST_DATA "/dives/TestDivingLog4.1.1.sql", &_sqlite3_handle); if (ret == 0) ret = parse_divinglog_buffer(_sqlite3_handle, 0, 0, 0, &divelog); else fprintf(stderr, "Can't open sqlite3 db: " SUBSURFACE_TEST_DATA "/dives/TestDivingLog4.1.1.sql"); return ret; } int TestParse::parseV2NoQuestion() { // parsing of a V2 file should work return parse_file(SUBSURFACE_TEST_DATA "/dives/test40.xml", &divelog); } int TestParse::parseV3() { // parsing of a V3 files should succeed return parse_file(SUBSURFACE_TEST_DATA "/dives/test42.xml", &divelog); } void TestParse::testParse() { QCOMPARE(parseCSV(0, SUBSURFACE_TEST_DATA "/dives/test41.csv"), 0); fprintf(stderr, "number of dives %d \n", divelog.dives->nr); QCOMPARE(parseDivingLog(), 0); fprintf(stderr, "number of dives %d \n", divelog.dives->nr); QCOMPARE(parseV2NoQuestion(), 0); fprintf(stderr, "number of dives %d \n", divelog.dives->nr); QCOMPARE(parseV3(), 0); fprintf(stderr, "number of dives %d \n", divelog.dives->nr); sort_dive_table(divelog.dives); QCOMPARE(save_dives("./testout.ssrf"), 0); FILE_COMPARE("./testout.ssrf", SUBSURFACE_TEST_DATA "/dives/test40-42.xml"); } void TestParse::testParseDM4() { QCOMPARE(sqlite3_open(SUBSURFACE_TEST_DATA "/dives/TestDiveDM4.db", &_sqlite3_handle), 0); QCOMPARE(parse_dm4_buffer(_sqlite3_handle, 0, 0, 0, &divelog), 0); sort_dive_table(divelog.dives); QCOMPARE(save_dives("./testdm4out.ssrf"), 0); FILE_COMPARE("./testdm4out.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveDM4.xml"); } void TestParse::testParseDM5() { QCOMPARE(sqlite3_open(SUBSURFACE_TEST_DATA "/dives/TestDiveDM5.db", &_sqlite3_handle), 0); QCOMPARE(parse_dm5_buffer(_sqlite3_handle, 0, 0, 0, &divelog), 0); sort_dive_table(divelog.dives); QCOMPARE(save_dives("./testdm5out.ssrf"), 0); FILE_COMPARE("./testdm5out.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveDM5.xml"); } void TestParse::testParseHUDC() { xml_params params; xml_params_add_int(¶ms, "timeField", 0); xml_params_add_int(¶ms, "depthField", 1); xml_params_add_int(¶ms, "tempField", 5); xml_params_add_int(¶ms, "po2Field", -1); xml_params_add_int(¶ms, "o2sensor1Field", -1); xml_params_add_int(¶ms, "o2sensor2Field", -1); xml_params_add_int(¶ms, "o2sensor3Field", -1); xml_params_add_int(¶ms, "cnsField", -1); xml_params_add_int(¶ms, "ndlField", 2); xml_params_add_int(¶ms, "ttsField", -1); xml_params_add_int(¶ms, "stopdepthField", -1); xml_params_add_int(¶ms, "pressureField", -1); xml_params_add_int(¶ms, "setpointField", -1); xml_params_add_int(¶ms, "separatorIndex", 2); xml_params_add_int(¶ms, "units", 0); xml_params_add(¶ms, "hw", "\"DC text\""); QCOMPARE(parse_csv_file(SUBSURFACE_TEST_DATA "/dives/TestDiveSeabearHUDC.csv", ¶ms, "csv", &divelog), 0); QCOMPARE(divelog.dives->nr, 1); /* * CSV import uses time and date stamps relative to current * time, thus we need to use a static (random) timestamp */ if (divelog.dives->nr > 0) { struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; dive->when = 1255152761; dive->dc.when = 1255152761; } sort_dive_table(divelog.dives); QCOMPARE(save_dives("./testhudcout.ssrf"), 0); FILE_COMPARE("./testhudcout.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveSeabearHUDC.xml"); } void TestParse::testParseNewFormat() { QDir dir; QStringList filter; QStringList files; /* * Set the directory location and file filter for H3 CSV files. */ dir.setPath(QString::fromLatin1(SUBSURFACE_TEST_DATA "/dives")); filter << "TestDiveSeabearH3*.csv"; filter << "TestDiveSeabearT1*.csv"; files = dir.entryList(filter, QDir::Files); /* * Parse all files found matching the filter. */ for (int i = 0; i < files.size(); ++i) { QCOMPARE(parse_seabear_log(QString::fromLatin1(SUBSURFACE_TEST_DATA "/dives/") .append(files.at(i)) .toLatin1() .data(), &divelog), 0); QCOMPARE(divelog.dives->nr, i + 1); } sort_dive_table(divelog.dives); fprintf(stderr, "number of dives %d \n", divelog.dives->nr); QCOMPARE(save_dives("./testsbnewout.ssrf"), 0); // Currently the CSV parse fails FILE_COMPARE("./testsbnewout.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveSeabearNewFormat.xml"); } void TestParse::testParseDLD() { QString filename = SUBSURFACE_TEST_DATA "/dives/TestDiveDivelogsDE.DLD"; auto [mem, err] = readfile(filename.toLatin1().data()); QVERIFY(err > 0); QVERIFY(try_to_open_zip(filename.toLatin1().data(), &divelog) > 0); fprintf(stderr, "number of dives from DLD: %d \n", divelog.dives->nr); sort_dive_table(divelog.dives); // Compare output QCOMPARE(save_dives("./testdldout.ssrf"), 0); FILE_COMPARE("./testdldout.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveDivelogsDE.xml") } void TestParse::testParseMerge() { /* * check that we correctly merge mixed cylinder dives */ QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/ostc.xml", &divelog), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/vyper.xml", &divelog), 0); sort_dive_table(divelog.dives); QCOMPARE(save_dives("./testmerge.ssrf"), 0); FILE_COMPARE("./testmerge.ssrf", SUBSURFACE_TEST_DATA "/dives/mergedVyperOstc.xml"); } int TestParse::parseCSVmanual(int units, std::string file) { verbose = 1; xml_params params; // Numbers are column numbers xml_params_add_int(¶ms, "numberField", 0); xml_params_add_int(¶ms, "dateField", 1); xml_params_add_int(¶ms, "timeField", 2); xml_params_add_int(¶ms, "durationField", 3); // 4 Will be SAC, once we add support for reading it xml_params_add_int(¶ms, "maxDepthField", 5); xml_params_add_int(¶ms, "meanDepthField", 6); xml_params_add_int(¶ms, "modeField", 7); xml_params_add_int(¶ms, "airtempField", 8); xml_params_add_int(¶ms, "watertempField", 9); xml_params_add_int(¶ms, "cylindersizeField", 10); xml_params_add_int(¶ms, "startpressureField", 11); xml_params_add_int(¶ms, "endpressureField", 12); xml_params_add_int(¶ms, "o2Field", 13); xml_params_add_int(¶ms, "heField", 14); xml_params_add_int(¶ms, "locationField", 15); xml_params_add_int(¶ms, "gpsField", 16); xml_params_add_int(¶ms, "divemasterField", 17); xml_params_add_int(¶ms, "buddyField", 18); xml_params_add_int(¶ms, "suitField", 19); xml_params_add_int(¶ms, "notesField", 22); xml_params_add_int(¶ms, "weightField", 23); xml_params_add_int(¶ms, "tagsField", 24); // Numbers are indices of possible options xml_params_add_int(¶ms, "separatorIndex", 1); xml_params_add_int(¶ms, "datefmt", 2); xml_params_add_int(¶ms, "durationfmt", 2); xml_params_add_int(¶ms, "units", units); return parse_manual_file(file.c_str(), ¶ms, &divelog); } void TestParse::exportCSVDiveDetails() { int saved_sac = 0; parse_file(SUBSURFACE_TEST_DATA "/dives/test25.xml", &divelog); export_dives_xslt("testcsvexportmanual.csv", 0, 0, "xml2manualcsv.xslt", false); export_dives_xslt("testcsvexportmanualimperial.csv", 0, 1, "xml2manualcsv.xslt", false); if (divelog.dives->nr > 0) { struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; saved_sac = dive->sac; } clear_dive_file_data(); parseCSVmanual(1, "testcsvexportmanualimperial.csv"); // We do not currently support reading SAC, thus faking it if (divelog.dives->nr > 0) { struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; dive->sac = saved_sac; } sort_dive_table(divelog.dives); export_dives_xslt("testcsvexportmanual2.csv", 0, 0, "xml2manualcsv.xslt", false); FILE_COMPARE("testcsvexportmanual2.csv", "testcsvexportmanual.csv"); clear_dive_file_data(); } void TestParse::exportSubsurfaceCSV() { int saved_sac = 0; xml_params params; /* Test SubsurfaceCSV with multiple cylinders */ parse_file(SUBSURFACE_TEST_DATA "/dives/test40.xml", &divelog); export_dives_xslt("testcsvexportmanual-cyl.csv", 0, 0, "xml2manualcsv.xslt", false); export_dives_xslt("testcsvexportmanualimperial-cyl.csv", 0, 1, "xml2manualcsv.xslt", false); if (divelog.dives->nr > 0) { struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; saved_sac = dive->sac; } clear_dive_file_data(); xml_params_add_int(¶ms, "separatorIndex", 1); xml_params_add_int(¶ms, "units", 1); parse_csv_file("testcsvexportmanualimperial-cyl.csv", ¶ms, "SubsurfaceCSV", &divelog); // We do not currently support reading SAC, thus faking it if (divelog.dives->nr > 0) { struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; dive->sac = saved_sac; } sort_dive_table(divelog.dives); export_dives_xslt("testcsvexportmanual2-cyl.csv", 0, 0, "xml2manualcsv.xslt", false); FILE_COMPARE("testcsvexportmanual2-cyl.csv", "testcsvexportmanual-cyl.csv"); clear_dive_file_data(); } int TestParse::parseCSVprofile(int units, std::string file) { verbose = 1; xml_params params; // Numbers are column numbers xml_params_add_int(¶ms, "numberField", 0); xml_params_add_int(¶ms, "dateField", 1); xml_params_add_int(¶ms, "starttimeField", 2); xml_params_add_int(¶ms, "timeField", 3); xml_params_add_int(¶ms, "depthField", 4); xml_params_add_int(¶ms, "tempField", 5); xml_params_add_int(¶ms, "pressureField", 6); // Numbers are indices of possible options xml_params_add_int(¶ms, "datefmt", 2); xml_params_add_int(¶ms, "units", units); return parse_csv_file(file.c_str(), ¶ms, "csv", &divelog); } void TestParse::exportCSVDiveProfile() { parse_file(SUBSURFACE_TEST_DATA "/dives/test40.xml", &divelog); export_dives_xslt("testcsvexportprofile.csv", 0, 0, "xml2csv.xslt", false); export_dives_xslt("testcsvexportprofileimperial.csv", 0, 1, "xml2csv.xslt", false); clear_dive_file_data(); parseCSVprofile(1, "testcsvexportprofileimperial.csv"); sort_dive_table(divelog.dives); export_dives_xslt("testcsvexportprofile2.csv", 0, 0, "xml2csv.xslt", false); FILE_COMPARE("testcsvexportprofile2.csv", "testcsvexportprofile.csv"); clear_dive_file_data(); } void TestParse::exportUDDF() { parse_file(SUBSURFACE_TEST_DATA "/dives/test40.xml", &divelog); export_dives_xslt("testuddfexport.uddf", 0, 1, "uddf-export.xslt", false); clear_dive_file_data(); parse_file("testuddfexport.uddf", &divelog); sort_dive_table(divelog.dives); export_dives_xslt("testuddfexport2.uddf", 0, 1, "uddf-export.xslt", false); FILE_COMPARE("testuddfexport.uddf", "testuddfexport2.uddf"); clear_dive_file_data(); } void TestParse::testExport() { exportCSVDiveDetails(); exportSubsurfaceCSV(); exportCSVDiveProfile(); exportUDDF(); } void TestParse::parseDL7() { xml_params params; xml_params_add_int(¶ms, "dateField", -1); xml_params_add_int(¶ms, "datefmt", 0); xml_params_add_int(¶ms, "starttimeField", -1); xml_params_add_int(¶ms, "numberField", -1); xml_params_add_int(¶ms, "timeField", 1); xml_params_add_int(¶ms, "depthField", 2); xml_params_add_int(¶ms, "tempField", -1); xml_params_add_int(¶ms, "po2Field", -1); xml_params_add_int(¶ms, "o2sensor1Field", -1); xml_params_add_int(¶ms, "o2sensor2Field", -1); xml_params_add_int(¶ms, "o2sensor3Field", -1); xml_params_add_int(¶ms, "cnsField", -1); xml_params_add_int(¶ms, "ndlField", -1); xml_params_add_int(¶ms, "ttsField", -1); xml_params_add_int(¶ms, "stopdepthField", -1); xml_params_add_int(¶ms, "pressureField", -1); xml_params_add_int(¶ms, "setpointField", -1); xml_params_add_int(¶ms, "separatorIndex", 3); xml_params_add_int(¶ms, "units", 0); xml_params_add(¶ms, "hw", "DL7"); clear_dive_file_data(); QCOMPARE(parse_csv_file(SUBSURFACE_TEST_DATA "/dives/DL7.zxu", ¶ms, "DL7", &divelog), 0); QCOMPARE(divelog.dives->nr, 3); sort_dive_table(divelog.dives); QCOMPARE(save_dives("./testdl7out.ssrf"), 0); FILE_COMPARE("./testdl7out.ssrf", SUBSURFACE_TEST_DATA "/dives/DL7.xml"); clear_dive_file_data(); } QTEST_GUILESS_MAIN(TestParse)