base functional unit tests for transpiler

This commit is contained in:
jorenchik
2024-11-10 11:03:09 +02:00
parent 084f6afb13
commit f7f3ac5d4c
8 changed files with 481 additions and 32 deletions

View File

@@ -6,8 +6,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
set( set(
CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG
"${CMAKE_CXX_FLAGS_DEBUG} -g3 -O0 -ggdb -fvar-tracking-assignments -fno-inline" "${CMAKE_CXX_FLAGS_DEBUG} -g3 -O0 -ggdb -fno-inline"
) )
add_subdirectory(src/transpiler) add_subdirectory(src/transpiler)
add_subdirectory(src/qtapp) add_subdirectory(src/qtapp)
add_subdirectory(src/test)

23
src/test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.14)
project(MdemoryTest)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(GTest REQUIRED)
enable_testing()
add_executable(
testTranspiler
testTranspiler.cpp
)
target_link_libraries(
testTranspiler
GTest::gtest_main
api
)
include(GoogleTest)
gtest_discover_tests(testTranspiler)

363
src/test/testTranspiler.cpp Normal file
View File

@@ -0,0 +1,363 @@
#include "api.h"
#include "parser.h"
#include "result.h"
#include <gtest/gtest.h>
TEST(TranspilerTest, TestSimpleQuestion) {
std::string questionText = "- A question front >\n"
"- An answer\n"
"- Another answer";
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
auto questions = res.value.questions;
EXPECT_EQ(questions.size(), 1);
EXPECT_EQ(questions[0]->questionText, "A question front");
auto meQuestion = dynamic_cast<MultiElementQuestion*>(questions[0]);
EXPECT_EQ(meQuestion->type, Regular);
EXPECT_EQ(meQuestion->choices.size(), 2);
EXPECT_EQ(meQuestion->choices[0].answer, "An answer");
EXPECT_EQ(meQuestion->choices[1].answer, "Another answer");
EXPECT_EQ(meQuestion->choices[0].isCorrect, false);
EXPECT_EQ(meQuestion->choices[1].isCorrect, false);
delete meQuestion;
}
TEST(TranspilerTest, TestChoiceQuestion) {
std::string questionText = "- A question front >\n"
"+ An answer\n"
"- Another answer";
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
auto questions = res.value.questions;
EXPECT_EQ(questions.size(), 1);
EXPECT_EQ(questions[0]->questionText, "A question front");
auto meQuestion = dynamic_cast<MultiElementQuestion*>(questions[0]);
EXPECT_EQ(meQuestion->type, MultiChoice);
EXPECT_EQ(meQuestion->choices.size(), 2);
EXPECT_EQ(meQuestion->choices[0].answer, "An answer");
EXPECT_EQ(meQuestion->choices[1].answer, "Another answer");
EXPECT_EQ(meQuestion->choices[0].isCorrect, true);
EXPECT_EQ(meQuestion->choices[1].isCorrect, false);
delete meQuestion;
}
TEST(TranspilerTest, TestOrderQuestion) {
std::string questionText = "- A question front >\n"
"- An answer\n"
"-^ Another answer";
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
auto questions = res.value.questions;
EXPECT_EQ(questions.size(), 1);
EXPECT_EQ(questions[0]->questionText, "A question front");
auto meQuestion = dynamic_cast<MultiElementQuestion*>(questions[0]);
EXPECT_EQ(meQuestion->type, Order);
EXPECT_EQ(meQuestion->choices.size(), 2);
EXPECT_EQ(meQuestion->choices[0].answer, "An answer");
EXPECT_EQ(meQuestion->choices[1].answer, "Another answer");
EXPECT_EQ(meQuestion->choices[0].isCorrect, false);
EXPECT_EQ(meQuestion->choices[1].isCorrect, false);
delete meQuestion;
}
TEST(TranspilerTest, TestGroupQuestion) {
std::string questionText = "- A question front >\n"
"- Group 1:\n"
"- Element 1"
"- Element 2"
"- Group 2:\n"
"- Group 3:\n"
"- Element 1";
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
auto questions = res.value.questions;
EXPECT_EQ(questions.size(), 1);
EXPECT_EQ(questions[0]->questionText, "A question front");
auto grQuestion = dynamic_cast<GroupQuestion*>(questions[0]);
EXPECT_EQ(grQuestion->groups.size(), 3);
EXPECT_EQ(grQuestion->groups[0].name, "Group 1");
EXPECT_EQ(grQuestion->groups[1].name, "Group 2");
EXPECT_EQ(grQuestion->groups[2].name, "Group 3");
EXPECT_EQ(grQuestion->groups[0].elements.size(), 2);
EXPECT_EQ(grQuestion->groups[1].elements.size(), 0);
EXPECT_EQ(grQuestion->groups[2].elements.size(), 1);
delete grQuestion;
}
TEST(TranspilerTest, TestConflictingElements) {
std::string questionText;
Result<ParseInfo> res;
// Iespējamie elementu modifikatori:
// -^ secības
// + izvēles (pareiza atbilde)
// : grupas
// Apskatīsim visas konfliktējošas kombinācijas.
// -^ un :
questionText = "- A question front >\n"
"- An answer 1\n"
"-^ An answer 2\n"
"- An answer 3\n"
"- An answer 4:\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
// : un -^
questionText = "- A question front >\n"
"- An answer 1\n"
"- An answer 2:\n"
"- An answer 3\n"
"-^ An answer 4\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
// + un :
questionText = "- A question front >\n"
"- An answer 1\n"
"+ An answer 2\n"
"- An answer 3\n"
"- An answer 4:\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
// : un +
questionText = "- A question front >\n"
"- An answer 1\n"
"- An answer 2:\n"
"- An answer 3\n"
"+ An answer 4\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
// -^ un +
questionText = "- A question front >\n"
"- An answer 1\n"
"-^ An answer 2\n"
"- An answer 3\n"
"+ An answer 4\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
// + un -^
questionText = "- A question front >\n"
"- An answer 1\n"
"+ An answer 2\n"
"- An answer 3\n"
"-^ An answer 4\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
}
TEST(TranspilerTest, TestGroupQuestionWithLeadingElements) {
std::string questionText = "- A question front >\n"
"- Element 1\n"
"- Element 2\n"
"- Group 1:\n"
"- Element 1\n"
"- Element 2\n"
"- Element 3\n";
auto res = transpile(questionText);
EXPECT_NE(res.error, "");
}
TEST(TranspilerTest, TestIncorrectTimestamp) {
// Kontrole.
std::string questionText = "- A question front >\n"
"- Element 1\n"
"- Element 2\n";
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
// Pārbaude.
questionText = "Incorrect timestamp\n"
"- A question front >\n"
"- Element 1\n"
"- Element 2\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
}
TEST(TranspilerTest, TestUnexpectedCooldown) {
std::string questionText;
Result<ParseInfo> res;
// Kontrole.
questionText = "- A question front >\n"
"- Element 1\n"
"- Element 2\n";
res = transpile(questionText);
EXPECT_EQ(res.error, "");
// Pārbaude.
questionText = "- A question front >\n"
"- Element 1\n"
"- Element 2 [0.12]\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
}
TEST(TranspilerTest, TestCooldownParse) {
std::string questionText;
Result<ParseInfo> res;
Question *question;
// Ar punktu.
questionText = "- [12.21] A question front >\n"
"- Element 1\n"
"- Element 2\n";
res = transpile(questionText);
question = res.value.questions[0];
EXPECT_EQ(res.error, "");
EXPECT_EQ(question->cooldown, 12.21);
delete question;
// Bez punkta.
questionText = "- [12] A question front >\n"
"- Element 1\n"
"- Element 2\n";
res = transpile(questionText);
question = res.value.questions[0];
EXPECT_EQ(res.error, "");
EXPECT_EQ(question->cooldown, 12);
delete question;
// Negatīvs.
questionText = "- [-12.21] A question front >\n"
"- Element 1\n"
"- Element 2\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
// Nav skaitlis.
questionText = "- [NotANumber] A question front >\n"
"- Element 1\n"
"- Element 2\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
}
TEST(TranspilerTest, TestParsesTime) {
std::string questionText = "12.02.2022 12\\:25\n"
"- A question front >\n"
"- Element 1\n"
"- Element 2\n";
const time_t expectedTime = 1644668700;
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
EXPECT_EQ(res.value.lastTrainedAt, expectedTime);
}
TEST(TranspilerTest, TestOrderQuestionDuplicates) {
std::string questionText;
Result<ParseInfo> res;
// Kontrole.
questionText = "- A question front >\n"
"- Element 1\n"
"- Element 2\n"
"- Element 3\n"
"- Element 4\n"
"-^ Element 5\n";
res = transpile(questionText);
EXPECT_EQ(res.error, "");
// Pārbaude.
questionText = "- A question front >\n"
"- Element 1\n"
"- Element 2\n"
"- Element 3\n"
"- Element 1\n"
"-^ Element 5\n";
res = transpile(questionText);
EXPECT_NE(res.error, "");
}
TEST(TranspilerTest, TestRemovesExcessiveWhitespace) {
std::string questionText = "- \n\t A question\n\t front\n\t >\n"
"-\n\t An\n\t answer\n\t "
"-\n\t Another\n\t answer\n\t ";
auto res = transpile(questionText);
auto questions = res.value.questions;
EXPECT_EQ(questions[0]->questionText, "A question front");
auto meQuestion = dynamic_cast<MultiElementQuestion*>(questions[0]);
EXPECT_EQ(meQuestion->choices[0].answer, "An answer");
EXPECT_EQ(meQuestion->choices[1].answer, "Another answer");
delete meQuestion;
}
TEST(TranspilerTest, TestTranspilesMultipleQuestions) {
std::string questionText = "- Question 1 >\n"
"- Answer 1\n"
"- Answer 2\n"
"- Question 2 >\n"
"- Answer 1\n"
"+ Answer 2\n"
"- Question 3 >\n"
"- Answer 1\n"
"-^ Answer 2\n"
"- Question 4 >\n"
"- Group 1:\n"
"- Element 1\n"
"- Element 2\n";
auto res = transpile(questionText);
EXPECT_EQ(res.error, "");
auto questions = res.value.questions;
EXPECT_EQ(questions.size(), 4);
EXPECT_EQ(questions[0]->questionText, "Question 1");
EXPECT_EQ(questions[1]->questionText, "Question 2");
EXPECT_EQ(questions[2]->questionText, "Question 3");
EXPECT_EQ(questions[3]->questionText, "Question 4");
auto question = dynamic_cast<MultiElementQuestion*>(questions[0]);
EXPECT_EQ(question->type, Regular);
EXPECT_EQ(question->choices.size(), 2);
EXPECT_EQ(question->choices[0].answer, "Answer 1");
EXPECT_EQ(question->choices[1].answer, "Answer 2");
EXPECT_EQ(question->choices[0].isCorrect, false);
EXPECT_EQ(question->choices[1].isCorrect, false);
delete question;
question = dynamic_cast<MultiElementQuestion*>(questions[1]);
EXPECT_EQ(question->type, MultiChoice);
EXPECT_EQ(question->choices.size(), 2);
EXPECT_EQ(question->choices[0].answer, "Answer 1");
EXPECT_EQ(question->choices[1].answer, "Answer 2");
EXPECT_EQ(question->choices[0].isCorrect, false);
EXPECT_EQ(question->choices[1].isCorrect, true);
delete question;
question = dynamic_cast<MultiElementQuestion*>(questions[2]);
EXPECT_EQ(question->type, Order);
EXPECT_EQ(question->choices.size(), 2);
EXPECT_EQ(question->choices[0].answer, "Answer 1");
EXPECT_EQ(question->choices[1].answer, "Answer 2");
EXPECT_EQ(question->choices[0].isCorrect, false);
EXPECT_EQ(question->choices[1].isCorrect, false);
delete question;
auto groupQuestion = dynamic_cast<GroupQuestion*>(questions[3]);
EXPECT_EQ(groupQuestion->groups.size(), 1);
auto group = groupQuestion->groups[0];
EXPECT_EQ(group.name, "Group 1");
EXPECT_EQ(group.elements[0], "Element 1");
EXPECT_EQ(group.elements[1], "Element 2");
delete groupQuestion;
}

View File

@@ -23,14 +23,14 @@ bool sof;
/* /*
* TODO * TODO
*/ */
void trimString(std::string &str, std::string trimChars) { void trimString(std::string *str, std::string trimChars) {
// Noņem kreisās puses simbolus. // Noņem kreisās puses simbolus.
int padSize = 0; int padSize = 0;
bool pad = false; bool pad = false;
for (size_t i = 0; i < str.size(); ++i) { for (size_t i = 0; i < str->size(); ++i) {
for (size_t k = 0; k < trimChars.size(); ++k) { for (size_t k = 0; k < trimChars.size(); ++k) {
if (str[i] == trimChars[k]) { if ((*str)[i] == trimChars[k]) {
padSize++; padSize++;
pad = true; pad = true;
break; break;
@@ -42,15 +42,15 @@ void trimString(std::string &str, std::string trimChars) {
pad = false; pad = false;
} }
if (padSize > 0) { if (padSize > 0) {
str.erase(0, padSize); str->erase(0, padSize);
} }
// Noņem labās puses simbolus. // Noņem labās puses simbolus.
padSize = 0; padSize = 0;
pad = false; pad = false;
for (size_t i = str.size(); i-- > 0;) { for (size_t i = str->size(); i-- > 0;) {
for (size_t k = 0; k < trimChars.size(); ++k) { for (size_t k = 0; k < trimChars.size(); ++k) {
if (str[i] == trimChars[k]) { if ((*str)[i] == trimChars[k]) {
padSize++; padSize++;
pad = true; pad = true;
break; break;
@@ -62,7 +62,7 @@ void trimString(std::string &str, std::string trimChars) {
pad = false; pad = false;
} }
if (padSize > 0) { if (padSize > 0) {
str.erase(str.end() - padSize, str.end()); str->erase(str->end() - padSize, str->end());
} }
} }
@@ -79,7 +79,7 @@ void tokenWithBuffer(
std::string token(buffer.end() - tokenLen, buffer.end()); std::string token(buffer.end() - tokenLen, buffer.end());
if (buffer.size() > tokenLen) { if (buffer.size() > tokenLen) {
std::string prevFragment(buffer.begin(), buffer.end() - tokenLen); std::string prevFragment(buffer.begin(), buffer.end() - tokenLen);
trimString(prevFragment, " \n\t"); trimString(&prevFragment, " \n\t");
if (prevFragment.length() > 0) { if (prevFragment.length() > 0) {
tokens.push_back(Token{ tokens.push_back(Token{
textType, textType,

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>clang_version</key>
<string>clang version 18.1.8</string>
<key>diagnostics</key>
<array>
</array>
<key>files</key>
<array>
</array>
</dict>
</plist>

14
src/transpiler/main.plist Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>clang_version</key>
<string>clang version 18.1.8</string>
<key>diagnostics</key>
<array>
</array>
<key>files</key>
<array>
</array>
</dict>
</plist>

View File

@@ -144,25 +144,18 @@ Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
}; };
if (isInBounds(i) && tokens[i].tokenType == TokenType::TextFragment) { if (isInBounds(i) && tokens[i].tokenType == TokenType::TextFragment) {
try { const std::string format = "%d.%m.%Y %H:%M";
auto parseToUTCTime = [](const std::string datetime, std::string format) { const std::string datetime = tokens[i].content.c_str();
std::tm tm = {}; std::tm tm = {};
std::istringstream ss(datetime); std::istringstream ss(datetime);
ss >> std::get_time(&tm, format.c_str()); ss >> std::get_time(&tm, format.c_str());
if (ss.fail()) { if (ss.fail()) {
throw std::runtime_error("Neizdevās nolasīt datuma un laiku");
}
std::time_t time = timegm(&tm);
return time;
};
time = parseToUTCTime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M");
} catch (std::exception e) {
return makeResult( return makeResult(
e.what(), "Neizdevās nolasīt datuma un laiku",
tokens[i] tokens[i]
); );
} }
time = timegm(&tm);
i++; i++;
} }
@@ -171,17 +164,17 @@ Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
std::string questionText; std::string questionText;
std::vector<QuestionElement> questionElements; std::vector<QuestionElement> questionElements;
double cooldown; double cooldown;
bool isOrderQuestion = false; bool isOrderQuestion = false;
bool isGroupQuestion = false; bool isGroupQuestion = false;
bool isPlusQuestion = false; bool isPlusQuestion = false;
bool hasGroupEncountered = false;
Token questionStartToken; Token questionStartToken;
// Start element parsing & add to the offset. // Start element parsing & add to the offset.
if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
return makeResult( return makeResult(
"Nevar izmantot secības modifikatoru ('^') jautājuma sākumā", "Nevar izmantot secības modifikatoru ('^') jautājuma sākumā",
tokens[i + 1] tokens[i + 1]);
);
} }
questionStartToken = tokens[i]; questionStartToken = tokens[i];
@@ -235,6 +228,18 @@ Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
} else { } else {
isDash = false; isDash = false;
isPlusQuestion = true; isPlusQuestion = true;
if (isGroupQuestion) {
return makeResult(
"jautājumos ar grupām nevar būt secības elementu ('+' and ':')",
tokens[i]
);
}
if (isOrderQuestion) {
return makeResult(
"secības jautājumos nevar būt izvēles elementu ('-^' and '+')",
tokens[i]
);
}
} }
if (isInBounds(i+1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { if (isInBounds(i+1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
isOrder = true; isOrder = true;
@@ -247,7 +252,13 @@ Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
} }
if (isGroupQuestion) { if (isGroupQuestion) {
return makeResult( return makeResult(
"jautājumos ar grupām nevar būt secības elementu ('-^' and ':')", "jautājumos ar grupām nevar būt secības elementu ('-^' and ':')",
tokens[i]
);
}
if (isPlusQuestion) {
return makeResult(
"izvēles jautājumos nevar būt secības elementu ('+' and '-^')",
tokens[i] tokens[i]
); );
} }
@@ -259,14 +270,23 @@ Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
} }
} }
if (isInBounds(i + 2) && tokens[i + 2].tokenType == TokenType::MatchGroupEnd) { if (isInBounds(i + 2) && tokens[i + 2].tokenType == TokenType::MatchGroupEnd) {
isGroup = true; isGroup = true;
isGroupQuestion = true; isGroupQuestion = true;
if (!isDash) { if (!isDash) {
return makeResult( return makeResult(
"grupas jautājumus var definēt tikai ar svītru elementiem ('-')", "grupas jautājumus var definēt tikai ar svītru elementiem ('-')",
tokens[i] tokens[i]
); );
} }
if (!hasGroupEncountered) {
if (questionElements.size() > 0) {
return makeResult(
"elementi grupas jautājumā nevar eksistēt bez grupas",
tokens[i]
);
}
}
hasGroupEncountered = true;
} }
QuestionElement questionElement; QuestionElement questionElement;

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>clang_version</key>
<string>clang version 18.1.8</string>
<key>diagnostics</key>
<array>
</array>
<key>files</key>
<array>
</array>
</dict>
</plist>