You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
11 KiB
315 lines
11 KiB
#include <gtest/gtest.h> |
|
|
|
#include "data/file.hpp" |
|
#include "data/parser.hpp" |
|
|
|
#include <string_view> |
|
#include <vector> |
|
|
|
#include "utils/paths.h" |
|
|
|
namespace devilution { |
|
auto LoadDataFile(std::string_view file) |
|
{ |
|
std::string unitTestFolderCompletePath = paths::BasePath() + "/test/fixtures/"; |
|
paths::SetAssetsPath(unitTestFolderCompletePath); |
|
return DataFile::load(file); |
|
} |
|
|
|
void TestFileContents( |
|
const DataFile &dataFile, |
|
std::vector<std::vector<std::string_view>> expectedContent, |
|
GetFieldResult::Status expectedEndOfRecordStatus = GetFieldResult::Status::EndOfRecord, |
|
GetFieldResult::Status expectedEndOfFileStatus = GetFieldResult::Status::EndOfFile) |
|
{ |
|
GetFieldResult result { dataFile.begin() }; |
|
unsigned row = 0; |
|
do { |
|
ASSERT_LT(row, expectedContent.size()) << "Too many records"; |
|
unsigned col = 0; |
|
do { |
|
ASSERT_LT(col, expectedContent[row].size()) << "Too many fields in record " << row; |
|
result = GetNextField(result.next, dataFile.end()); |
|
EXPECT_EQ(result.value, expectedContent[row][col]) << "Unexpected value at record " << row << " and field " << col; |
|
col++; |
|
} while (!result.endOfRecord()); |
|
if (!result.endOfFile()) |
|
EXPECT_EQ(result.status, expectedEndOfRecordStatus) << "Unexpected status when parsing the end of record " << row; |
|
|
|
EXPECT_EQ(col, expectedContent[row].size()) << "Parsing returned fewer fields than expected in record " << row; |
|
row++; |
|
} while (!result.endOfFile()); |
|
EXPECT_EQ(result.status, expectedEndOfFileStatus) << "Unexpected status when parsing the end of the file"; |
|
|
|
EXPECT_EQ(row, expectedContent.size()) << "Parsing returned fewer records than expected"; |
|
} |
|
|
|
TEST(DataFileTest, TryLoadMissingFile) |
|
{ |
|
auto result = LoadDataFile("txtdata\\not_found.tsv"); |
|
EXPECT_FALSE(result.has_value()) << "Trying to load a non-existent file should return an unexpected/error response"; |
|
|
|
EXPECT_EQ(result.error(), DataFile::Error::NotFound) << "The error code should indicate the file was not found"; |
|
} |
|
|
|
TEST(DataFileTest, LoadCRFile) |
|
{ |
|
auto result = LoadDataFile("txtdata\\cr.tsv"); |
|
ASSERT_TRUE(result.has_value()) << "Unable to load cr.tsv"; |
|
|
|
DataFile &dataFile = result.value(); |
|
EXPECT_EQ(dataFile.size(), 33) << "File size should be reported in code units"; |
|
|
|
std::vector<std::vector<std::string_view>> expectedFields { |
|
{ "Test", "Empty", "Values" }, |
|
{ "", "2", "3" }, |
|
{ "1", "2", "" }, |
|
{ "1", "", "3" } |
|
// because the file does not end with a newline parsing stops at the previous record |
|
}; |
|
|
|
TestFileContents(dataFile, expectedFields, GetFieldResult::Status::BadRecordSeparator, GetFieldResult::Status::FileTruncated); |
|
} |
|
|
|
TEST(DataFileTest, LoadWindowsFile) |
|
{ |
|
auto result = LoadDataFile("txtdata\\crlf.tsv"); |
|
ASSERT_TRUE(result.has_value()) << "Unable to load crlf.tsv"; |
|
|
|
DataFile &dataFile = result.value(); |
|
EXPECT_EQ(dataFile.size(), 37) << "File size should be reported in code units"; |
|
|
|
std::vector<std::vector<std::string_view>> expectedFields { |
|
{ "Test", "Empty", "Values" }, |
|
{ "", "2", "3" }, |
|
{ "1", "2", "" }, |
|
{ "1", "", "3" }, |
|
{ "" } // file ends with a newline, parsing returns a single empty field |
|
}; |
|
|
|
TestFileContents(dataFile, expectedFields); |
|
} |
|
|
|
TEST(DataFileTest, LoadTypicalFile) |
|
{ |
|
auto result = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = result.value(); |
|
EXPECT_EQ(dataFile.size(), 33) << "File size should be reported in code units"; |
|
|
|
std::vector<std::vector<std::string_view>> expectedFields { |
|
{ "Test", "Empty", "Values" }, |
|
{ "", "2", "3" }, |
|
{ "1", "2", "" }, |
|
{ "1", "", "3" }, |
|
{ "" } // file ends with a newline, parsing returns a single empty field |
|
}; |
|
|
|
TestFileContents(dataFile, expectedFields); |
|
} |
|
|
|
TEST(DataFileTest, LoadUtf8WithBOM) |
|
{ |
|
auto result = LoadDataFile("txtdata\\utf8_bom.tsv"); |
|
ASSERT_TRUE(result.has_value()) << "Unable to load utf8_bom.tsv"; |
|
|
|
DataFile &dataFile = result.value(); |
|
EXPECT_EQ(dataFile.size(), 33) << "Loading a file containing a UTF8 byte order marker should strip that prefix"; |
|
|
|
std::vector<std::vector<std::string_view>> expectedFields { |
|
{ "Test", "Empty", "Values" }, |
|
{ "", "2", "3" }, |
|
{ "1", "2", "" }, |
|
{ "1", "", "3" }, |
|
{ "" } // file ends with a newline, parsing returns a single empty field |
|
}; |
|
|
|
TestFileContents(dataFile, expectedFields); |
|
} |
|
|
|
TEST(DataFileTest, IterateOverRecords) |
|
{ |
|
auto result = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = result.value(); |
|
|
|
std::vector<std::vector<std::string_view>> expectedFields { |
|
{ "Test", "Empty", "Values" }, |
|
{ "", "2", "3" }, |
|
{ "1", "2", "" }, |
|
{ "1", "", "3" }, |
|
{ "" } // file ends with a newline, parsing returns a single empty field |
|
}; |
|
|
|
unsigned row = 0; |
|
for (FieldsInRecordRange record : dataFile.records()) { |
|
ASSERT_LT(row, expectedFields.size()) << "Too many records"; |
|
unsigned col = 0; |
|
for (std::string_view field : record) { |
|
if (col < expectedFields[row].size()) |
|
EXPECT_EQ(field, expectedFields[row][col]) << "Unexpected value at record " << row << " and field " << col; |
|
else |
|
ADD_FAILURE() << "Extra value '" << field << "' in record " << row << " at field " << col; |
|
col++; |
|
} |
|
EXPECT_GE(col, expectedFields[row].size()) << "Parsing returned fewer fields than expected in record " << row; |
|
row++; |
|
} |
|
EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; |
|
} |
|
|
|
TEST(DataFileTest, DiscardAllAfterFirstField) |
|
{ |
|
auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = loadDataResult.value(); |
|
|
|
std::array<std::string_view, 5> expectedFields { "Test", "", "1", "1", "" }; |
|
|
|
GetFieldResult result { dataFile.begin() }; |
|
unsigned row = 0; |
|
do { |
|
ASSERT_LT(row, expectedFields.size()) << "Too many records"; |
|
result = GetNextField(result.next, dataFile.end()); |
|
EXPECT_EQ(result.value, expectedFields[row]) << "Unexpected first value at record " << row; |
|
|
|
if (result.endOfRecord() && !result.endOfFile()) |
|
ADD_FAILURE() << "Parsing returned fewer fields than expected in record " << row; |
|
else |
|
result = DiscardRemainingFields(result.next, dataFile.end()); |
|
row++; |
|
} while (!result.endOfFile()); |
|
|
|
EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; |
|
} |
|
|
|
TEST(DataFileTest, DiscardAllAfterFirstFieldIterator) |
|
{ |
|
auto result = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = result.value(); |
|
|
|
std::array<std::string_view, 5> expectedFields { "Test", "", "1", "1", "" }; |
|
|
|
unsigned row = 0; |
|
for (FieldsInRecordRange record : dataFile.records()) { |
|
ASSERT_LT(row, expectedFields.size()) << "Too many records"; |
|
for (std::string_view field : record) { |
|
EXPECT_EQ(field, expectedFields[row]) << "Unexpected first value at record " << row; |
|
break; |
|
} |
|
row++; |
|
} |
|
} |
|
|
|
TEST(DataFileTest, DiscardAllUpToLastField) |
|
{ |
|
auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = loadDataResult.value(); |
|
|
|
std::array<std::string_view, 5> expectedFields { "Values", "3", "", "3", "" }; |
|
|
|
GetFieldResult result { dataFile.begin() }; |
|
unsigned row = 0; |
|
do { |
|
ASSERT_LT(row, expectedFields.size()) << "Too many records"; |
|
result = DiscardMultipleFields(result.next, dataFile.end(), 2); |
|
if (row < expectedFields.size() - 1) { |
|
EXPECT_FALSE(result.endOfRecord()) << "Parsing returned fewer fields than expected in record " << row; |
|
if (!result.endOfRecord()) |
|
result = GetNextField(result.next, dataFile.end()); |
|
} |
|
EXPECT_EQ(result.value, expectedFields[row]) << "Unexpected last value at record " << row; |
|
|
|
if (!result.endOfRecord()) { |
|
ADD_FAILURE() << "Parsing returned fewer fields than expected in record " << row; |
|
result = DiscardRemainingFields(result.next, dataFile.end()); |
|
} |
|
row++; |
|
} while (!result.endOfFile()); |
|
|
|
EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; |
|
} |
|
|
|
TEST(DataFileTest, SkipFieldIterator) |
|
{ |
|
auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = loadDataResult.value(); |
|
|
|
// we don't actually test the last value as incrementing the fields-in-record iterator past |
|
// the last field invalidates the value, we can't recover the same way the procedural interface does. |
|
std::array<std::string_view, 5> expectedFields { "Values", "3", "", "3", "" }; |
|
|
|
RecordsRange recordsRange = dataFile.records(); |
|
auto record = recordsRange.begin(); |
|
auto endRecord = recordsRange.end(); |
|
unsigned row = 0; |
|
while (record != endRecord) { |
|
ASSERT_LT(row, expectedFields.size()) << "Too many records"; |
|
FieldsInRecordRange fieldRange = *record; |
|
auto field = fieldRange.begin(); |
|
auto endField = fieldRange.end(); |
|
field += 2; |
|
if (row < expectedFields.size() - 1) { |
|
if (field == endField) { |
|
ADD_FAILURE() << "Parsing returned fewer fields than expected in record " << row; |
|
} else { |
|
EXPECT_EQ(*field, expectedFields[row]) << "Unexpected last value at record " << row; |
|
++field; |
|
} |
|
} |
|
EXPECT_EQ(field, endField) << "Parsing returned more fields than expected in record " << row; |
|
|
|
++row; |
|
++record; |
|
} |
|
|
|
EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; |
|
} |
|
|
|
TEST(DataFileTest, SkipRowIterator) |
|
{ |
|
auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); |
|
ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; |
|
|
|
DataFile &dataFile = loadDataResult.value(); |
|
|
|
std::vector<std::vector<std::string_view>> expectedFields { |
|
// skipping the first two lines |
|
{ "1", "2", "" }, |
|
{ "1", "", "3" }, |
|
{ "" } // file ends with a newline, parsing returns a single empty field |
|
}; |
|
|
|
RecordsRange recordsRange = dataFile.records(); |
|
auto record = recordsRange.begin(); |
|
auto endRecord = recordsRange.end(); |
|
record += 2; |
|
unsigned row = 0; |
|
while (record != endRecord) { |
|
ASSERT_LT(row, expectedFields.size()) << "Too many records"; |
|
unsigned col = 0; |
|
for (std::string_view field : *record) { |
|
if (col < expectedFields[row].size()) |
|
EXPECT_EQ(field, expectedFields[row][col]) << "Unexpected value at record " << row << " and field " << col; |
|
else |
|
ADD_FAILURE() << "Extra value '" << field << "' in record " << row << " at field " << col; |
|
col++; |
|
} |
|
EXPECT_GE(col, expectedFields[row].size()) << "Parsing returned fewer fields than expected in record " << row; |
|
++row; |
|
++record; |
|
} |
|
|
|
EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; |
|
} |
|
|
|
} // namespace devilution
|
|
|