Commit b125ba7f authored by Andreas Cord-Landwehr's avatar Andreas Cord-Landwehr
Browse files

Add unit tests for skeleton resource

parent 4384576e
......@@ -82,6 +82,20 @@ target_link_libraries(test_courseresource
add_test(test_courseresource test_courseresource)
ecm_mark_as_test(test_courseresource)
# test skeleton resource class
set(TestSkeletonResource_SRCS
skeletonresource/test_skeletonresource.cpp
skeletonresource/resourcerepositorystub.cpp
)
qt5_add_resources(TestSkeletonResource_SRCS ../data/languages.qrc)
add_executable(test_skeletonresource ${TestSkeletonResource_SRCS} )
target_link_libraries(test_skeletonresource
artikulatecore
Qt5::Test
)
add_test(test_skeletonresource test_skeletonresource)
ecm_mark_as_test(test_skeletonresource)
# test editable course resource class
set(TestEditableCourseResource_SRCS
editablecourseresource/test_editablecourseresource.cpp
......
......@@ -54,12 +54,6 @@ void TestCourseResource::courseSchemeValidationTest()
QXmlSchema courseSchema;
QVERIFY(courseSchema.load(schemeFile));
QVERIFY(courseSchema.isValid());
//TODO shall be used in skeleton specific test
QUrl skeletonFile = QUrl::fromLocalFile(QStringLiteral("schemes/skeleton.xsd"));
QXmlSchema skeletonScheme;
QVERIFY(skeletonScheme.load(skeletonFile));
QVERIFY(skeletonScheme.isValid());
}
void TestCourseResource::loadCourseResource()
......
/*
* Copyright 2013 Andreas Cord-Landwehr <cordlandwehr@kde.org>
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -18,35 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SKELETON_H
#define SKELETON_H
#include "resourcerepositorystub.h"
#include "artikulatecore_export.h"
#include "resources/editablecourseresource.h"
#include <QObject>
#include <QMap>
#include <QUrl>
class ResourceInterface;
class SkeletonResource;
class ARTIKULATECORE_EXPORT Skeleton : public EditableCourseResource
{
Q_OBJECT
public:
explicit Skeleton(SkeletonResource *resource = nullptr);
/**
* Writes course object back to file and set \ref modified state to false.
* If no file is set, no operation is performed.
*/
Q_INVOKABLE void sync();
private:
Q_DISABLE_COPY(Skeleton)
SkeletonResource * const m_resource;
};
#endif // SKELETON_H
// define one virtual method out of line to pin CourseStub to this translation unit
ResourceRepositoryStub::~ResourceRepositoryStub() = default;
/*
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RESOURCEREPOSITORYSTUB_H
#define RESOURCEREPOSITORYSTUB_H
#include "core/iresourcerepository.h"
#include <QObject>
#include <QVector>
class Language;
class ICourse;
/**
* @brief The ResourceRepositoryStub that only provides languages and a storage location, but nothing else
*/
class ResourceRepositoryStub : public IResourceRepository
{
Q_OBJECT
public:
ResourceRepositoryStub(QVector<Language *> languages)
{
m_languages = languages;
}
~ResourceRepositoryStub() override;
QString storageLocation() const override
{
return m_storageLocation;
}
QVector<ICourse *> courses() const override
{
return QVector<ICourse *>(); // do not return any courses: stub shall only provide languages
}
QVector<ICourse *> courses(Language *language) const override
{
Q_UNUSED(language);
return QVector<ICourse *>(); // do not return any courses: stub shall only provide languages
}
void reloadCourses() override
{
; // do nothing, stub shall only provide languages
}
QVector<Language *> languages() const override
{
return m_languages;
}
Q_SIGNALS:
void courseAboutToBeAdded(ICourse*,int) override;
void courseAdded() override;
void courseAboutToBeRemoved(int) override;
void courseRemoved() override;
private:
QString m_storageLocation;
QVector<Language *> m_languages;
};
#endif
/*
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "test_skeletonresource.h"
#include "resourcerepositorystub.h"
#include "core/language.h"
#include "core/unit.h"
#include "core/phrase.h"
#include "core/resources/languageresource.h"
#include "core/resources/skeletonresource.h"
#include <QTest>
#include <QDebug>
#include <QTemporaryFile>
#include <QSignalSpy>
#include <QIODevice>
#include <QFile>
#include <QXmlSchema>
#include <QXmlSchemaValidator>
#include <QDomDocument>
TestSkeletonResource::TestSkeletonResource()
{
}
void TestSkeletonResource::init()
{
}
void TestSkeletonResource::cleanup()
{
}
void TestSkeletonResource::schemeValidationTest()
{
QUrl skeletonFile = QUrl::fromLocalFile(QStringLiteral("schemes/skeleton.xsd"));
QXmlSchema skeletonScheme;
QVERIFY(skeletonScheme.load(skeletonFile));
QVERIFY(skeletonScheme.isValid());
}
void TestSkeletonResource::loadSkeletonResource()
{
Language language;
language.setId("de");
ResourceRepositoryStub repository({&language});
const QString courseDirectory = "data/contributorrepository/skeletons/";
const QString courseFile = courseDirectory + "skeleton.xml";
SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository);
QCOMPARE(course.file().toLocalFile(), courseFile);
QCOMPARE(course.id(), "skeleton-testdata");
QCOMPARE(course.foreignId(), "skeleton-testdata"); // always same as ID
QCOMPARE(course.title(), "Artikulate Test Course Title");
QCOMPARE(course.description(), "Artikulate Test Course Description");
QVERIFY(course.language() == nullptr); // a skeleton must not have a language
QCOMPARE(course.unitList().count(), 2);
const auto unit = course.unitList().first();
QVERIFY(unit != nullptr);
QCOMPARE(unit->id(), "{11111111-b885-4833-97ff-27cb1ca2f543}");
QCOMPARE(unit->title(), QStringLiteral("Numbers"));
QCOMPARE(unit->phraseList().count(), 2);
// note: this test takes the silent assumption that phrases are added to the list in same
// order as they are defined in the file. This assumption should be made explicit or dropped
const auto firstPhrase = unit->phraseList().first();
QVERIFY(firstPhrase != nullptr);
QCOMPARE(firstPhrase->id(), "{22222222-9234-4da5-a6fe-dbd5104f57d5}");
QCOMPARE(firstPhrase->text(), "0");
QCOMPARE(firstPhrase->type(), Phrase::Type::Word);
const auto secondPhrase = unit->phraseList().at(1);
QVERIFY(secondPhrase != nullptr);
QCOMPARE(secondPhrase->id(), "{333333333-b4a9-4264-9a26-75a55aa5d302}");
QCOMPARE(secondPhrase->text(), "1");
QCOMPARE(secondPhrase->type(), Phrase::Type::Word);
}
void TestSkeletonResource::unitAddAndRemoveHandling()
{
// boilerplate
Language language;
language.setId("de");
ResourceRepositoryStub repository({&language});
const QString courseDirectory = "data/courses/de/";
const QString courseFile = courseDirectory + "de.xml";
SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository);
// begin of test
Unit unit;
unit.setId("testunit");
const int initialUnitNumber = course.unitList().count();
QCOMPARE(initialUnitNumber, 1);
QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(Unit*, int)));
QSignalSpy spyAdded(&course, SIGNAL(unitAdded()));
QCOMPARE(spyAboutToBeAdded.count(), 0);
QCOMPARE(spyAdded.count(), 0);
course.addUnit(&unit);
QCOMPARE(course.unitList().count(), initialUnitNumber + 1);
QCOMPARE(spyAboutToBeAdded.count(), 1);
QCOMPARE(spyAdded.count(), 1);
}
void TestSkeletonResource::coursePropertyChanges()
{
// boilerplate
Language language;
language.setId("de");
ResourceRepositoryStub repository({&language});
const QString courseDirectory = "data/courses/de/";
const QString courseFile = courseDirectory + "de.xml";
SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository);
// id
{
const QString value = "newId";
QSignalSpy spy(&course, SIGNAL(idChanged()));
QCOMPARE(spy.count(), 0);
course.setId(value);
QCOMPARE(course.id(), value);
QCOMPARE(spy.count(), 1);
}
// title
{
const QString value = "newTitle";
QSignalSpy spy(&course, SIGNAL(titleChanged()));
QCOMPARE(spy.count(), 0);
course.setTitle(value);
QCOMPARE(course.title(), value);
QCOMPARE(spy.count(), 1);
}
// description
{
const QString value = "newDescription";
QSignalSpy spy(&course, SIGNAL(descriptionChanged()));
QCOMPARE(spy.count(), 0);
course.setDescription(value);
QCOMPARE(course.description(), value);
QCOMPARE(spy.count(), 1);
}
}
// FIXME porting break
//void TestCourseResource::fileLoadSaveCompleteness()
//{
// ResourceManager manager;
// manager.addLanguage(QUrl::fromLocalFile(QStringLiteral("data/languages/de.xml")));
// manager.addCourse(QUrl::fromLocalFile(QStringLiteral("data/courses/de.xml")));
// // test to encure further logic
// QVERIFY(manager.courseResources(manager.languageResources().constFirst()->language()).count() == 1);
// Course *testCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constFirst()->course();
// QTemporaryFile outputFile;
// outputFile.open();
// QUrl oldFileName = testCourse->file();
// testCourse->setFile(QUrl::fromLocalFile(outputFile.fileName()));
// testCourse->setLanguage(manager.languageResources().constFirst()->language());
// testCourse->sync();
// testCourse->setFile(oldFileName); // restore for later tests
// QFile file(outputFile.fileName());
// if (!file.open(QIODevice::ReadOnly)) {
// qCritical() << "Could not open file to read.";
// }
// //TODO this only works, since the resource manager not checks uniqueness of course ids!
// manager.addCourse(QUrl::fromLocalFile(outputFile.fileName()));
// Course *compareCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constLast()->course();
// // test that we actually call the different files
// QVERIFY(testCourse->file().toLocalFile() != compareCourse->file().toLocalFile());
// QVERIFY(testCourse->id() == compareCourse->id());
// QVERIFY(testCourse->foreignId() == compareCourse->foreignId());
// QVERIFY(testCourse->title() == compareCourse->title());
// QVERIFY(testCourse->description() == compareCourse->description());
// QVERIFY(testCourse->language()->id() == compareCourse->language()->id());
// QVERIFY(testCourse->unitList().count() == compareCourse->unitList().count());
// Unit *testUnit = testCourse->unitList().constFirst();
// Unit *compareUnit = compareCourse->unitList().constFirst();
// QVERIFY(testUnit->id() == compareUnit->id());
// QVERIFY(testUnit->foreignId() == compareUnit->foreignId());
// QVERIFY(testUnit->title() == compareUnit->title());
// QVERIFY(testUnit->phraseList().count() == compareUnit->phraseList().count());
// Phrase *testPhrase = testUnit->phraseList().constFirst();
// Phrase *comparePhrase = new Phrase(this);
// // Note that this actually means that we DO NOT respect phrase orders by list order!
// foreach (Phrase *phrase, compareUnit->phraseList()) {
// if (testPhrase->id() == phrase->id()) {
// comparePhrase = phrase;
// break;
// }
// }
// QVERIFY(testPhrase->id() == comparePhrase->id());
// QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId());
// QVERIFY(testPhrase->text() == comparePhrase->text());
// QVERIFY(testPhrase->type() == comparePhrase->type());
// QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName());
// QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count());
// //FIXME implement phoneme checks after phonemes are fully implemented
//}
QTEST_GUILESS_MAIN(TestSkeletonResource)
/*
* Copyright 2013 Andreas Cord-Landwehr <cordlandwehr@kde.org>
* Copyright 2013-2019 Andreas Cord-Landwehr <cordlandwehr@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -18,31 +18,58 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "skeleton.h"
#include "resources/skeletonresource.h"
#include "resources/editablecourseresource.h"
#include "unit.h"
#include "language.h"
#include "phonemegroup.h"
#include "artikulate_debug.h"
#include <KLocalizedString>
#include <QStringList>
#include <QPair>
#include <QUuid>
Skeleton::Skeleton(SkeletonResource *resource)
: EditableCourseResource(QUrl(), nullptr) //FIXME thus just compiles but not more
, m_resource(resource)
{
}
#ifndef TESTSKELETONRESOURCE_H
#define TESTSKELETONRESOURCE_H
#include <QObject>
#include <QVariant>
void Skeleton::sync()
class TestSkeletonResource : public QObject
{
if (!file().isValid() || file().isEmpty() || m_resource == nullptr) {
qCWarning(ARTIKULATE_LOG()) << "No file path set, aborting sync operation.";
return;
}
m_resource->sync();
setModified(false);
}
Q_OBJECT
public:
TestSkeletonResource();
private slots:
/**
* @brief Called before every test case.
*/
void init();
/**
* @brief Called after every test case.
*/
void cleanup();
/**
* @brief Test if course XSD specification is valid.
*/
void schemeValidationTest();
/**
* @brief Test simple loading of course resource XML file
*/
void loadSkeletonResource();
/**
* @brief Test handling of unit insertions (specifically, the signals)
*/
void unitAddAndRemoveHandling();
/**
* @brief Test of all course property changes except unit handling
*/
void coursePropertyChanges();
/**
* Test if serialization of unserialized file gives original file.
* TODO this is a test by only string equality and should improved to test on a data level
*/
// void fileLoadSaveCompleteness();
private:
bool m_systemUseCourseRepositoryValue;
};
#endif
<?xml version="1.0"?>
<skeleton>
<id>skeleton-testdata</id>
<title>Artikulate Test Course Title</title>
<description>Artikulate Test Course Description</description>
<units>
<unit>
<id>{11111111-b885-4833-97ff-27cb1ca2f543}</id>
<title>Numbers</title>
<phrases>
<phrase>
<id>{22222222-9234-4da5-a6fe-dbd5104f57d5}</id>
<text>0</text>
<type>word</type>
</phrase>
<phrase>
<id>{333333333-b4a9-4264-9a26-75a55aa5d302}</id>
<text>1</text>
<type>word</type>
</phrase>
</phrases>
</unit>
<unit>
<id>{44444444-f531-4ffc-a73d-7677dcf6693a}</id>
<title>At a Party</title>
<phrases>
<phrase>
<id>{55555555-95ee-4b9d-813d-204b6f60b61a}</id>
<text>talk</text>
<type>word</type>
</phrase>
</phrases>
</unit>
</units>
</skeleton>
......@@ -49,7 +49,6 @@ set(artikulateCore_SRCS
core/phoneme.cpp
core/phonemegroup.cpp
core/unit.cpp
core/skeleton.cpp
core/editorsession.cpp
core/trainingaction.cpp
core/trainingactionicon.cpp
......
......@@ -31,7 +31,6 @@
#include "core/phrase.h"
#include "core/player.h"
#include "core/recorder.h"
#include "core/skeleton.h"
#include "core/trainingsession.h"
#include "core/unit.h"
#include "core/resources/editablecourseresource.h"
......@@ -110,7 +109,6 @@ void Application::registerQmlTypes()
qmlRegisterType<LearnerProfile::Learner>("artikulate", 1, 0, "Learner");
qmlRegisterType<LearnerProfile::LearningGoal>("artikulate", 1, 0, "LearningGoal");
qmlRegisterType<Unit>("artikulate", 1, 0, "Unit");
qmlRegisterType<Skeleton>("artikulate", 1, 0, "Skeleton");
qmlRegisterType<Language>("artikulate", 1, 0, "Language");
qmlRegisterType<Phrase>("artikulate", 1, 0, "Phrase");
qmlRegisterType<Phoneme>("artikulate", 1, 0, "Phoneme");
......
......@@ -18,3 +18,4 @@
#include "artikulate_debug.h"
Q_LOGGING_CATEGORY(ARTIKULATE_LOG, "log_artikulate")
Q_LOGGING_CATEGORY(ARTIKULATE_CORE, "articulate.core")
Q_LOGGING_CATEGORY(ARTIKULATE_PARSER, "articulate.parser")
......@@ -21,5 +21,6 @@
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(ARTIKULATE_LOG)
Q_DECLARE_LOGGING_CATEGORY(ARTIKULATE_CORE)
Q_DECLARE_LOGGING_CATEGORY(ARTIKULATE_PARSER)
#endif
......@@ -21,7 +21,6 @@
#include "contributorrepository.h"
#include "artikulate_debug.h"
#include "language.h"
#include "skeleton.h"
#include "unit.h"
#include "phrase.h"
#include "phoneme.h"
......@@ -84,7 +83,7 @@ bool ContributorRepository::modified() const
}
}
foreach (auto const &courseRes, m_skeletonResources) {
if (courseRes->isOpen() && courseRes->skeleton()->isModified()) {
if (courseRes->isModified()) {
return true;
}
}
......@@ -224,9 +223,9 @@ void ContributorRepository::reloadCourseOrSkeleton(ICourse *courseOrSkeleton)
removeCourse(courseOrSkeleton);
addCourse(file);
} else {
foreach (SkeletonResource *resource, m_skeletonResources) {
if (resource->identifier() == courseOrSkeleton->id()) {
resource->reload();
for (SkeletonResource *resource : m_skeletonResources) {
if (resource->id() == courseOrSkeleton->id()) {
// TODO no reload available
return;
}
}
......@@ -295,13 +294,11 @@ void ContributorRepository::updateCourseFromSkeleton(EditableCourseResource *cou
return;
}
ICourse *skeleton = nullptr;
QList<SkeletonResource *>::ConstIterator iter = m_skeletonResources.constBegin();
while (iter != m_skeletonResources.constEnd()) {
if ((*iter)->identifier() == course->foreignId()) {
skeleton = (*iter)->skeleton();
for (const auto &iter : m_skeletonResources) {
if (iter->id() == course->foreignId()) {
skeleton = iter;
break;
}
++iter;
}
if (!skeleton) {
qCritical() << "Could not find skeleton with id " << course->foreignId() << ", aborting update.";
......@@ -406,7 +403,7 @@ void ContributorRepository::removeCourse(ICourse *course)
}
}
EditableCourseResource * ContributorRepository::createCourse(Language *language, Skeleton *skeleton)
EditableCourseResource * ContributorRepository::createCourse(Language *language, SkeletonResource *skeleton)