Merge branch 'master' into refac/memory

This commit is contained in:
Hyunjin Song
2024-10-03 17:05:29 +09:00
2625 changed files with 674162 additions and 293334 deletions

View File

@@ -1,33 +1,34 @@
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}")
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}")
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include")
INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}")
INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}/src")
include(CTest)
SET(CMAKE_CXX_STANDARD 14)
SET(CMAKE_AUTOMOC ON)
# FIXME: remove this once we export include directories for LMMS
IF(LMMS_BUILD_APPLE)
INCLUDE_DIRECTORIES("/usr/local/include")
ENDIF()
ADD_EXECUTABLE(tests
EXCLUDE_FROM_ALL
main.cpp
QTestSuite
$<TARGET_OBJECTS:lmmsobjs>
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(LMMS_TESTS
src/core/ArrayVectorTest.cpp
src/core/AutomatableModelTest.cpp
src/core/MathTest.cpp
src/core/ProjectVersionTest.cpp
src/core/RelativePathsTest.cpp
src/core/MemoryPoolTest.cpp
src/tracks/AutomationTrackTest.cpp
)
TARGET_COMPILE_DEFINITIONS(tests
PRIVATE $<TARGET_PROPERTY:lmmsobjs,INTERFACE_COMPILE_DEFINITIONS>
)
TARGET_LINK_LIBRARIES(tests ${QT_LIBRARIES} ${QT_QTTEST_LIBRARY})
TARGET_LINK_LIBRARIES(tests ${LMMS_REQUIRED_LIBS})
foreach(LMMS_TEST_SRC IN LISTS LMMS_TESTS)
# TODO CMake 3.20: Use cmake_path
get_filename_component(LMMS_TEST_NAME ${LMMS_TEST_SRC} NAME_WE)
add_executable(${LMMS_TEST_NAME} ${LMMS_TEST_SRC})
add_test(NAME ${LMMS_TEST_NAME} COMMAND ${LMMS_TEST_NAME})
# TODO CMake 3.12: Propagate usage requirements by linking to lmmsobjs
target_include_directories(${LMMS_TEST_NAME} PRIVATE $<TARGET_PROPERTY:lmmsobjs,INCLUDE_DIRECTORIES>)
target_static_libraries("${LMMS_TEST_NAME}" PRIVATE lmmsobjs)
target_link_libraries(${LMMS_TEST_NAME} PRIVATE
${QT_LIBRARIES}
${QT_QTTEST_LIBRARY}
)
target_compile_features(${LMMS_TEST_NAME} PRIVATE cxx_std_17)
endforeach()

View File

@@ -1,19 +0,0 @@
#include "QTestSuite.h"
QList<QTestSuite*> QTestSuite::m_suites;
QTestSuite::QTestSuite(QObject *parent) : QObject(parent)
{
m_suites << this;
}
QTestSuite::~QTestSuite()
{
m_suites.removeAll(this);
}
QList<QTestSuite*> QTestSuite::suites()
{
return m_suites;
}

View File

@@ -1,21 +0,0 @@
#ifndef QTESTSUITE_H
#define QTESTSUITE_H
#include <QtTest/QTest>
#include <QObject>
#include <QList>
class QTestSuite : public QObject
{
Q_OBJECT
public:
explicit QTestSuite(QObject *parent = 0);
~QTestSuite();
static QList<QTestSuite*> suites();
private:
static QList<QTestSuite*> m_suites;
};
#endif // QTESTSUITE_H

View File

@@ -5,7 +5,7 @@
<song>
<trackcontainer width="600" x="5" y="5" maximized="0" height="300" visible="1" type="song" minimized="0" >
<track muted="0" type="0" name="Standard-Preset" >
<instrumenttrack pan="0" fxch="0" pitch="0" basenote="57" vol="100" >
<instrumenttrack pan="0" mixch="0" pitch="0" basenote="57" vol="100" >
<instrument name="tripleoscillator" >
<tripleoscillator phoffset2="0" userwavefile0="" finer0="0" userwavefile1="" finer1="0" userwavefile2="" finer2="0" coarse0="0" coarse1="0" coarse2="0" finel0="0" finel1="0" modalgo1="2" modalgo2="2" finel2="0" pan0="0" modalgo3="2" pan1="0" stphdetun0="0" pan2="0" stphdetun1="0" wavetype0="0" stphdetun2="0" wavetype1="0" wavetype2="0" vol0="33" vol1="33" phoffset0="0" phoffset1="0" vol2="33" />
</instrument>
@@ -30,7 +30,7 @@
<bbtrack>
<trackcontainer width="504" x="610" y="5" maximized="0" height="300" visible="1" type="bbtrackcontainer" minimized="0" >
<track muted="0" type="0" name="Standard-Preset" >
<instrumenttrack pan="0" fxch="0" pitch="0" basenote="57" vol="100" >
<instrumenttrack pan="0" mixch="0" pitch="0" basenote="57" vol="100" >
<instrument name="tripleoscillator" >
<tripleoscillator phoffset2="0" userwavefile0="" finer0="0" userwavefile1="" finer1="0" userwavefile2="" finer2="0" coarse0="0" coarse1="0" coarse2="0" finel0="0" finel1="0" modalgo1="2" modalgo2="2" finel2="0" pan0="0" modalgo3="2" pan1="0" stphdetun0="0" pan2="0" stphdetun1="0" wavetype0="0" stphdetun2="0" wavetype1="0" wavetype2="0" vol0="33" vol1="33" phoffset0="0" phoffset1="0" vol2="33" />
</instrument>
@@ -68,203 +68,203 @@
<object id="948063" />
</automationpattern>
</track>
<fxmixer width="865" x="5" y="310" maximized="0" height="278" visible="1" minimized="0" >
<fxchannel num="0" muted="0" volume="1" name="Master" >
<mixer width="865" x="5" y="310" maximized="0" height="278" visible="1" minimized="0" >
<mixerchannel num="0" muted="0" volume="1" name="Master" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="1" muted="0" volume="1" name="FX 1" >
</mixerchannel>
<mixerchannel num="1" muted="0" volume="1" name="Channel 1" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="2" muted="0" volume="1" name="FX 2" >
</mixerchannel>
<mixerchannel num="2" muted="0" volume="1" name="Channel 2" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="3" muted="0" volume="1" name="FX 3" >
</mixerchannel>
<mixerchannel num="3" muted="0" volume="1" name="Channel 3" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="4" muted="0" volume="1" name="FX 4" >
</mixerchannel>
<mixerchannel num="4" muted="0" volume="1" name="Channel 4" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="5" muted="0" volume="1" name="FX 5" >
</mixerchannel>
<mixerchannel num="5" muted="0" volume="1" name="Channel 5" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="6" muted="0" volume="1" name="FX 6" >
</mixerchannel>
<mixerchannel num="6" muted="0" volume="1" name="Channel 6" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="7" muted="0" volume="1" name="FX 7" >
</mixerchannel>
<mixerchannel num="7" muted="0" volume="1" name="Channel 7" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="8" muted="0" volume="1" name="FX 8" >
</mixerchannel>
<mixerchannel num="8" muted="0" volume="1" name="Channel 8" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="9" muted="0" volume="1" name="FX 9" >
</mixerchannel>
<mixerchannel num="9" muted="0" volume="1" name="Channel 9" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="10" muted="0" volume="1" name="FX 10" >
</mixerchannel>
<mixerchannel num="10" muted="0" volume="1" name="Channel 10" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="11" muted="0" volume="1" name="FX 11" >
</mixerchannel>
<mixerchannel num="11" muted="0" volume="1" name="Channel 11" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="12" muted="0" volume="1" name="FX 12" >
</mixerchannel>
<mixerchannel num="12" muted="0" volume="1" name="Channel 12" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="13" muted="0" volume="1" name="FX 13" >
</mixerchannel>
<mixerchannel num="13" muted="0" volume="1" name="Channel 13" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="14" muted="0" volume="1" name="FX 14" >
</mixerchannel>
<mixerchannel num="14" muted="0" volume="1" name="Channel 14" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="15" muted="0" volume="1" name="FX 15" >
</mixerchannel>
<mixerchannel num="15" muted="0" volume="1" name="Channel 15" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="16" muted="0" volume="1" name="FX 16" >
</mixerchannel>
<mixerchannel num="16" muted="0" volume="1" name="Channel 16" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="17" muted="0" volume="1" name="FX 17" >
</mixerchannel>
<mixerchannel num="17" muted="0" volume="1" name="Channel 17" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="18" muted="0" volume="1" name="FX 18" >
</mixerchannel>
<mixerchannel num="18" muted="0" volume="1" name="Channel 18" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="19" muted="0" volume="1" name="FX 19" >
</mixerchannel>
<mixerchannel num="19" muted="0" volume="1" name="Channel 19" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="20" muted="0" volume="1" name="FX 20" >
</mixerchannel>
<mixerchannel num="20" muted="0" volume="1" name="Channel 20" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="21" muted="0" volume="1" name="FX 21" >
</mixerchannel>
<mixerchannel num="21" muted="0" volume="1" name="Channel 21" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="22" muted="0" volume="1" name="FX 22" >
</mixerchannel>
<mixerchannel num="22" muted="0" volume="1" name="Channel 22" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="23" muted="0" volume="1" name="FX 23" >
</mixerchannel>
<mixerchannel num="23" muted="0" volume="1" name="Channel 23" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="24" muted="0" volume="1" name="FX 24" >
</mixerchannel>
<mixerchannel num="24" muted="0" volume="1" name="Channel 24" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="25" muted="0" volume="1" name="FX 25" >
</mixerchannel>
<mixerchannel num="25" muted="0" volume="1" name="Channel 25" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="26" muted="0" volume="1" name="FX 26" >
</mixerchannel>
<mixerchannel num="26" muted="0" volume="1" name="Channel 26" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="27" muted="0" volume="1" name="FX 27" >
</mixerchannel>
<mixerchannel num="27" muted="0" volume="1" name="Channel 27" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="28" muted="0" volume="1" name="FX 28" >
</mixerchannel>
<mixerchannel num="28" muted="0" volume="1" name="Channel 28" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="29" muted="0" volume="1" name="FX 29" >
</mixerchannel>
<mixerchannel num="29" muted="0" volume="1" name="Channel 29" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="30" muted="0" volume="1" name="FX 30" >
</mixerchannel>
<mixerchannel num="30" muted="0" volume="1" name="Channel 30" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="31" muted="0" volume="1" name="FX 31" >
</mixerchannel>
<mixerchannel num="31" muted="0" volume="1" name="Channel 31" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="32" muted="0" volume="1" name="FX 32" >
</mixerchannel>
<mixerchannel num="32" muted="0" volume="1" name="Channel 32" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="33" muted="0" volume="1" name="FX 33" >
</mixerchannel>
<mixerchannel num="33" muted="0" volume="1" name="Channel 33" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="34" muted="0" volume="1" name="FX 34" >
</mixerchannel>
<mixerchannel num="34" muted="0" volume="1" name="Channel 34" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="35" muted="0" volume="1" name="FX 35" >
</mixerchannel>
<mixerchannel num="35" muted="0" volume="1" name="Channel 35" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="36" muted="0" volume="1" name="FX 36" >
</mixerchannel>
<mixerchannel num="36" muted="0" volume="1" name="Channel 36" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="37" muted="0" volume="1" name="FX 37" >
</mixerchannel>
<mixerchannel num="37" muted="0" volume="1" name="Channel 37" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="38" muted="0" volume="1" name="FX 38" >
</mixerchannel>
<mixerchannel num="38" muted="0" volume="1" name="Channel 38" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="39" muted="0" volume="1" name="FX 39" >
</mixerchannel>
<mixerchannel num="39" muted="0" volume="1" name="Channel 39" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="40" muted="0" volume="1" name="FX 40" >
</mixerchannel>
<mixerchannel num="40" muted="0" volume="1" name="Channel 40" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="41" muted="0" volume="1" name="FX 41" >
</mixerchannel>
<mixerchannel num="41" muted="0" volume="1" name="Channel 41" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="42" muted="0" volume="1" name="FX 42" >
</mixerchannel>
<mixerchannel num="42" muted="0" volume="1" name="Channel 42" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="43" muted="0" volume="1" name="FX 43" >
</mixerchannel>
<mixerchannel num="43" muted="0" volume="1" name="Channel 43" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="44" muted="0" volume="1" name="FX 44" >
</mixerchannel>
<mixerchannel num="44" muted="0" volume="1" name="Channel 44" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="45" muted="0" volume="1" name="FX 45" >
</mixerchannel>
<mixerchannel num="45" muted="0" volume="1" name="Channel 45" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="46" muted="0" volume="1" name="FX 46" >
</mixerchannel>
<mixerchannel num="46" muted="0" volume="1" name="Channel 46" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="47" muted="0" volume="1" name="FX 47" >
</mixerchannel>
<mixerchannel num="47" muted="0" volume="1" name="Channel 47" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="48" muted="0" volume="1" name="FX 48" >
</mixerchannel>
<mixerchannel num="48" muted="0" volume="1" name="Channel 48" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="49" muted="0" volume="1" name="FX 49" >
</mixerchannel>
<mixerchannel num="49" muted="0" volume="1" name="Channel 49" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="50" muted="0" volume="1" name="FX 50" >
</mixerchannel>
<mixerchannel num="50" muted="0" volume="1" name="Channel 50" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="51" muted="0" volume="1" name="FX 51" >
</mixerchannel>
<mixerchannel num="51" muted="0" volume="1" name="Channel 51" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="52" muted="0" volume="1" name="FX 52" >
</mixerchannel>
<mixerchannel num="52" muted="0" volume="1" name="Channel 52" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="53" muted="0" volume="1" name="FX 53" >
</mixerchannel>
<mixerchannel num="53" muted="0" volume="1" name="Channel 53" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="54" muted="0" volume="1" name="FX 54" >
</mixerchannel>
<mixerchannel num="54" muted="0" volume="1" name="Channel 54" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="55" muted="0" volume="1" name="FX 55" >
</mixerchannel>
<mixerchannel num="55" muted="0" volume="1" name="Channel 55" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="56" muted="0" volume="1" name="FX 56" >
</mixerchannel>
<mixerchannel num="56" muted="0" volume="1" name="Channel 56" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="57" muted="0" volume="1" name="FX 57" >
</mixerchannel>
<mixerchannel num="57" muted="0" volume="1" name="Channel 57" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="58" muted="0" volume="1" name="FX 58" >
</mixerchannel>
<mixerchannel num="58" muted="0" volume="1" name="Channel 58" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="59" muted="0" volume="1" name="FX 59" >
</mixerchannel>
<mixerchannel num="59" muted="0" volume="1" name="Channel 59" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="60" muted="0" volume="1" name="FX 60" >
</mixerchannel>
<mixerchannel num="60" muted="0" volume="1" name="Channel 60" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="61" muted="0" volume="1" name="FX 61" >
</mixerchannel>
<mixerchannel num="61" muted="0" volume="1" name="Channel 61" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="62" muted="0" volume="1" name="FX 62" >
</mixerchannel>
<mixerchannel num="62" muted="0" volume="1" name="Channel 62" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="63" muted="0" volume="1" name="FX 63" >
</mixerchannel>
<mixerchannel num="63" muted="0" volume="1" name="Channel 63" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
<fxchannel num="64" muted="0" volume="1" name="FX 64" >
</mixerchannel>
<mixerchannel num="64" muted="0" volume="1" name="Channel 64" >
<fxchain numofeffects="0" enabled="0" />
</fxchannel>
</fxmixer>
</mixerchannel>
</mixer>
<controllerrackview width="258" x="880" y="310" maximized="0" height="278" visible="1" minimized="0" />
<pianoroll width="840" x="-11" y="0" maximized="0" height="480" visible="0" minimized="0" />
<automationeditor width="740" x="0" y="0" maximized="0" height="480" visible="0" minimized="0" />

View File

@@ -1,54 +0,0 @@
#include "QTestSuite.h"
#include <QtTest/QTest>
#include <QDebug>
#include "Engine.h"
void printAvailableSuits()
{
QStringList suitNames;
for (QTestSuite* suite : QTestSuite::suites()) {
suitNames.push_back(suite->metaObject()->className());
}
qDebug().noquote() << "Available test suits are: " << suitNames.join(", ");
}
int runSuit(const QString& name, const QStringList& args)
{
for (QTestSuite* suite : QTestSuite::suites()) {
if (suite->metaObject()->className() == name) {
return QTest::qExec(suite, args);
}
}
throw std::invalid_argument("No such test suit " + name.toStdString());
}
int main(int argc, char* argv[])
{
auto app = new QCoreApplication(argc, argv);
Engine::init(true);
int rc = 0;
if (app->arguments().size() > 1) {
try {
runSuit(app->arguments()[1], app->arguments().mid(1));
} catch (const std::invalid_argument& e) {
qDebug().noquote() << e.what();
printAvailableSuits();
rc = -1;
}
} else {
int numsuites = QTestSuite::suites().size();
qDebug() << ">> Will run" << numsuites << "test suites";
for (QTestSuite* suite : QTestSuite::suites()) {
rc += QTest::qExec(suite, argc, argv);
}
qDebug() << "<<" << rc << "out of"<<numsuites<<"test suites failed.";
}
Engine::destroy();
return rc;
}

24
tests/scripted/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Scripted checks
The checks in this directory are not C++ unit tests, but simple scripts that
check the code for issues.
## Verify
[verify](verify) is a verification script for the scripts in this folder.
## Check strings
[check-strings](check-strings) checks for invalid strings not necessarily in
the code.
Background: When you move a lot of classes and files, the code may be OK, but
many strings outside the code (translations, style.css, .gitmodules etc.) may
still need to be updated. This script checks whether these strings are still
matching the code.
## Check namespace
[check-namespace](check-namespace) checks namespaces and a few related things
like `#ifdef`s.

199
tests/scripted/check-namespace Executable file
View File

@@ -0,0 +1,199 @@
#!/usr/bin/python3
# This script checks the code for namespace related issues
# Note: This script is not perfect. It can not parse all LMMS files,
# and does not contain a complete C++ parser. If you encounter
# difficulties with this tests, it could be a fault of your
# changes, but it could also be a fault of this script.
import re
import subprocess
import sys
from pathlib import Path
from typing import NamedTuple
# types
class BlockType:
pass
IF_MACRO = BlockType()
HEADER_GUARD = BlockType()
CODE_BLOCK = BlockType()
EXTERN = BlockType()
class Expectation(NamedTuple):
type: BlockType
statement: str # expected end statement
name: str # expected name in comment
# global variables
errors = 0
# functions
def caption(my_str):
print(f'\n# {my_str}\n')
def error(where, match, my_str):
global errors
errors += 1
if match:
line = match.string[:match.start()].count('\n') + 1 # first line is 1
where = f'{where}:{line}'
print(f'Error: {where}: {my_str}')
if not Path('.gitmodules').is_file():
print('You need to call this script from the LMMS top directory')
exit(1)
result = subprocess.run(['git', 'ls-files', '*.[ch]', '*.[ch]pp', ':!tests/*'],
capture_output=True, text=True, check=True)
known_no_namespace_lmms = {
# main.cpp
'src/core/main.cpp',
# nothing to set under a namespace
'include/debug.h',
'include/versioninfo.h',
'plugins/CarlaBase/CarlaConfig/config.h',
'plugins/CarlaBase/DummyCarla.cpp',
# unclear why it has no namespace
'plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp',
}
exclude_files = re.compile(
# not ours:
'include/(aeffectx|fenv|ladspa).h|'
'plugins/LadspaEffect/(calf|caps|cmt|swh|tap)/|'
'plugins/MidiExport/MidiFile.hpp|'
'plugins/ReverbSC/[a-z]|'
'plugins/Sf2Player/fluidsynthshims.h|'
'/portsmf/|'
# only forward to headers that are not ours:
'plugins/ZynAddSubFx/ThreadShims.h'
)
files = [Path(f) for f in result.stdout.splitlines() if not exclude_files.search(f)]
# Debug argument
if len(sys.argv) > 1:
files = [Path(arg) for arg in sys.argv[1:]]
statement_pattern = re.compile(
# Capture comments first to prevent them from matching any other regex
# Include next line if line ends with backslash
r'/[*](.|\n)*?[*]/|//(.*\\\n)*.*|'
# Macro with <30 lines and no other #if inside needs no end comment
# Match here to prevent from matching next regex
# With a (?!negative lookahead) we can allow all # except the ones followed by "if"
r'^ *# *(?P<short_macro>ifn?def)(?=(([^#\n]|#(?!if|endif))*\n){,30} *# *endif)|'
# Macro followed by name or comment
# With (?P<name>...) you can do a backreference to \name later
r'^ *# *(?P<named_macro>ifn?def|endif)(( *// *| +)(?P<macro_name>\w+))?|'
# Other macros where we don't want the argument
r'^ *# *(?P<macro>include|if|el(se|if))|'
# Namespace that contains no other braces needs no end comment
# With a (?=lookahead) we can let the "namespace" part be eaten by the parser
# but save the braces and their content for later so they can be matched again
r'^ *namespace *[\w:]*\s*(?={[^{}]*})|'
# Start of named namespace, extern "C" or just a opening brace
r'(^ *(namespace +(?P<namespace>[\w:]+)|(?P<extern>extern *"C"))\s*)?(?P<opening_brace>{)|'
# End of namespace including comment, or just a closing brace
r'(?P<closing_brace>})( *// *namespace +(?P<namespace_end>[\w:]+))?'
# In all the regexes above match both tab and space when a space is used
r''.replace(' ', r'[\t ]'),
# Make ^ match on every line, not just beginning of file
re.MULTILINE)
# Comments and whitespace followed by header guard
header_guard_pattern = re.compile(r'^(/[*](.|\n)*?[*]/|//.*|\s)*#\s*(ifndef|pragma\s+once)')
# Namespace lmms
namespace_pattern = re.compile(r'^\s*namespace\s+lmms', re.MULTILINE)
#
# the real code
#
caption('namespace checks')
for cur_file in files:
if cur_file.is_file():
cur_text = cur_file.read_text(errors='replace')
if cur_file.as_posix() not in known_no_namespace_lmms:
namespace_pattern.search(cur_text) or error(cur_file, None, f'File has no namespace lmms')
header_guard = str(cur_file).endswith('.h')
expectations = [] # type: list[Expectation]
if header_guard:
if not header_guard_pattern.match(cur_text):
error(cur_file, None, 'First statement should be header guard')
header_guard = False
for m in statement_pattern.finditer(cur_text):
# Find the matched regex group
statement = m.group('opening_brace') or m.group('closing_brace')
if not statement:
statement = '#' + (m.group('macro') or m.group('named_macro') or m.group('short_macro') or '')
if not statement:
continue
# Start statements
if statement == '{':
etype = EXTERN if m.group('extern') else CODE_BLOCK
expectations.append(Expectation(etype, '}', m.group('namespace')))
elif statement.startswith('#if'):
etype = HEADER_GUARD if header_guard else IF_MACRO
expectations.append(Expectation(etype, '#endif', m.group('macro_name')))
header_guard = False
# End statements
elif statement == '#endif' or statement.startswith('#el') or statement == '}':
if not expectations:
error(cur_file, m, f'Unexpected {statement}')
break
if statement.startswith('#el') and expectations[-1].statement == '#endif':
# After an #else or #elif the comment is no longer mandatory, since it's hard to define
expectations[-1] = Expectation(IF_MACRO, '#endif', '')
# Don't pop the expectations, we are still waiting for the #endif
continue
exp = expectations.pop()
name = m.group('macro_name') or m.group('namespace_end') or ''
if statement != exp.statement:
error(cur_file, m, f'Expected {exp.statement} before {statement}')
break
# Require no end comment for header guard
elif exp.type is HEADER_GUARD and not name:
continue
elif exp.name and name != exp.name:
comment = 'namespace ' if exp.type is CODE_BLOCK else ''
error(cur_file, m, f'Missing comment // {comment}{exp.name}')
# Extra checks
elif statement == '#include':
if any(True for e in expectations if e.type is CODE_BLOCK):
error(cur_file, m, '#include inside a code block')
else:
# Leftover expected statements
for exp in reversed(expectations):
error(cur_file, None, f'Expected {exp.statement} before end of file')
caption('summary')
print(f'{str(errors)} errors.')
exit(1 if errors > 0 else 0)

151
tests/scripted/check-strings Executable file
View File

@@ -0,0 +1,151 @@
#!/usr/bin/python3
# This script checks for strings like paths or class names that are *not* in the source code, but e.g. in
# translation files, stylesheets or git files.
# Invalid strings *in the source code* are usually recognized when you compile them, but other strings may
# be overseen, which is why this script checks strings *outside of the source code*.
import re
import subprocess
import xml.etree.ElementTree as ElementTree
from pathlib import Path
import tinycss2
# global variables
errors = 0
# functions
def caption(my_str):
print(f'\n# {my_str}\n')
def error(where, my_str):
global errors
errors += 1
print(f'Error: {where}: {my_str}')
# if a string makes a classname, we need to check if that class is in the source
# however, some of these strings name classes that are not from LMMS, so these can be ignored for such checks:
def is_our_class(classname: str) -> int:
return classname[0] != 'Q' # Qt classes
# prepare some variables
if not Path('.gitmodules').is_file():
print('You need to call this script from the LMMS top directory')
exit(1)
result = subprocess.run(['git', 'ls-files', '*.[ch]', '*.[ch]pp', '*.ui', ':!tests/*'],
capture_output=True, text=True, check=True)
files = [Path(f) for f in result.stdout.splitlines()]
carlabase = 'carlabase' if Path('plugins/carlabase').is_dir() else 'CarlaBase'
carlapath = f'plugins/{carlabase}/carla/'
result = subprocess.run(['git',
'--git-dir', f'{carlapath}/.git',
'--work-tree', f'{carlapath}',
'ls-files', 'resources/ui', 'source/frontend'], capture_output=True, text=True, check=True)
files.extend([Path(f'{carlapath}/{f}') for f in result.stdout.splitlines()])
classes = set()
class_pat = re.compile(r'^\s*class(?:\s+LMMS_EXPORT)?\s+([a-zA-Z_][a-zA-Z0-9_]*)', re.MULTILINE)
class_pat_ui = re.compile(r'<class>([a-zA-Z_][a-zA-Z0-9_]*)</class>')
for cur_file in files:
if cur_file.is_file():
text = cur_file.read_text(errors='replace')
classes.update(re.findall(class_pat_ui if cur_file.suffix == '.ui' else class_pat, text))
# the real checks
caption('.gitmodules')
for p in re.findall(r'\[submodule "([^"]+)"\]\s*$', Path('.gitmodules').read_text(errors='replace'), re.MULTILINE):
if not Path(p).is_dir():
error('.gitmodules', f'Directory does not exist: {p}')
caption('locale')
classes_found = set()
for cur_file in Path('data/locale').glob('*.ts'):
tree = ElementTree.parse(str(cur_file))
root = tree.getroot()
for location in root.findall('./context/name'):
classes_found.add(location.text)
for c in sorted(classes_found):
if is_our_class(c) and '::' not in c and c not in classes:
error('data/locale', f'Class does not exist in source code: {c}')
caption('themes')
GUI_NAMESPACE_PREFIX = "lmms--gui"
def unscope_classname(stylesheet, cname):
# Strip the namespace part from the given class name,
# while expecting it to have one in the first place.
SCOPE_TOKEN = "--"
i = cname.rfind(SCOPE_TOKEN) + len(SCOPE_TOKEN)
assert i>=0
return cname[i:]
for theme in sorted([d for d in Path('data/themes').iterdir() if d.is_dir()]):
classes_in_sheet = set()
stylesheet = theme / 'style.css'
rules = tinycss2.parse_stylesheet(Path(stylesheet).read_text(errors='replace'))
for rule in rules:
if rule.type == 'qualified-rule':
class_found = False
for c in rule.prelude:
if c.type == 'ident' and not class_found:
if is_our_class(c.value):
if str(c.value).startswith(GUI_NAMESPACE_PREFIX):
classes_in_sheet.add(unscope_classname(stylesheet, c.value))
else:
error(stylesheet.as_posix(), f"Namespace prefix missing from class {c.value}")
class_found = True
# After whitespace or comma comes a new class
elif c.type == 'whitespace' or (c.type == 'literal' and c.value == ','):
class_found = False
missing_classes = classes_in_sheet - classes
for class_in_sheet in sorted(missing_classes):
error(stylesheet.as_posix(), f'Class does not exist in source code: {class_in_sheet}')
caption('patches (checks only plugins/)')
pat = re.compile(r'/(plugins/\S*)', re.MULTILINE)
calf = re.compile(r'calf/.*/modules\.') # these are a bit complicated to fix...
for cur_file in sorted(Path('.').glob('*/patches/*.patch')):
if Path(cur_file).is_file():
paths_in_patches = set()
for line in pat.findall(cur_file.read_text(errors='replace')):
if not calf.search(line):
paths_in_patches.add(Path(line))
for mpath in sorted(paths_in_patches):
# in case of LADSPA SWH effects, check that the XML exists, not the C file
# (because the C files are not generated until a build is done)
if mpath.parent == Path('plugins/LadspaEffect/swh/ladspa/'):
mpath = mpath.with_suffix('.xml')
if not mpath.is_file():
error(cur_file.as_posix(), f'Source file does not exist: {mpath.as_posix()}')
# summary
caption('summary')
print(f'{str(errors)} errors.')
exit(1 if errors > 0 else 0)

231
tests/scripted/verify Executable file
View File

@@ -0,0 +1,231 @@
#!/usr/bin/python3
import subprocess
from pathlib import Path
import tempfile
import os
import sys
def set_git_config():
"""Set local git identity, but only in local repo (to not overwrite user settings)"""
"""Also, fix git issue: fatal: transport 'file' not allowed"""
subprocess.run(['git', 'config', 'user.name', 'James Bond'])
subprocess.run(['git', 'config', 'user.email', '007@sis.gov.uk'])
subprocess.run(['git', 'config', '--global', 'protocol.file.allow', 'always'])
def create_file(filename: str, file_content: str):
"""Create a file in the current directory and adds it to git"""
Path(filename).parent.mkdir(parents=True, exist_ok=True)
with open(filename, "w", encoding='utf-8') as textfile:
print(file_content, file=textfile)
subprocess.run(['git', 'add', filename], check=True)
class ScriptTest():
def __init__(self, scriptpath: Path):
self.scriptpath = Path(scriptpath)
def __enter__(self):
"""Create temporary, minimal test environment, and change to it"""
self.lmms_tmpdir = tempfile.TemporaryDirectory(dir='.')
os.chdir(self.lmms_tmpdir.name)
# prerequirements
Path('data/themes').mkdir(parents=True)
subprocess.run(['git', 'init', '-b', 'main'], check=True)
set_git_config()
subprocess.run(['git', 'submodule', 'add', '../../carla', 'plugins/CarlaBase/carla'], check=True)
create_file('src/core/classes.cpp', 'namespace lmms {\nclass TestClass\n}')
create_file('data/locale/de.ts',
'<?xml version="1.0" ?><!DOCTYPE TS><TS language="de" version="2.1">\n'
' <context>\n'
' <name>TestClass</name>\n'
' <message>\n'
' <location filename="../../src/core/classes.cpp" line="20"/>\n'
' <source>About LMMS</source>\n'
' <translation>Über LMMS</translation>\n'
' </message>\n'
'</context>\n'
'</TS>\n')
subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True)
return self
def __exit__(self, type, value, traceback):
"""Leave and destroy temporary test environment"""
os.chdir('..')
self.lmms_tmpdir.cleanup()
def expect(self, expectation: str):
"""Check if "expectation" is in the output"""
if expectation not in self.result.stdout:
raise RuntimeError(f'Expected "{expectation}" in script output')
def run(self, expected_returncode: int = 1): # default: something goes wrong ("to the safe side")
"""Run the script, check the exit code and store the result"""
command = [str(self.scriptpath)]
if os.name == 'nt':
command.insert(0, sys.executable)
self.result = subprocess.run(command, capture_output=True, text=True)
print('--->8--- Script output BEGIN --->8---')
print(self.result.stdout)
print('--->8--- Script output END --->8---')
if self.result.stderr:
print('--->8--- Script error output BEGIN --->8---')
print(self.result.stderr)
print('--->8--- Script error output END --->8---')
# make sure script returned "error" (because we test for errors) and that the output is as expected
if self.result.returncode != expected_returncode:
raise RuntimeError(f"Script \"check-strings\" returned {self.result.returncode}, "
f"but {expected_returncode} expected")
lmms_main_path = Path(__file__).resolve().parent.parent.parent
with tempfile.TemporaryDirectory() as tmpdir:
os.chdir(tmpdir)
check_strings = lmms_main_path / 'tests' / 'scripted' / 'check-strings'
check_namespace = lmms_main_path / 'tests' / 'scripted' / 'check-namespace'
# create dummy carla repo
Path('carla').mkdir()
os.chdir('carla')
subprocess.run(['git', 'init', '-b', 'main'], check=True)
set_git_config()
create_file('README.md', 'hello world')
subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True)
os.chdir('..')
Path('lmms').mkdir()
os.chdir('lmms')
# minimal working example
with ScriptTest(check_strings) as test:
test.run(0) # exitcode 0 - no errors expected
test.expect('0 errors')
with ScriptTest(check_strings) as test:
create_file('data/locale/fr.ts',
'<?xml version="1.0" ?><!DOCTYPE TS><TS language="de" version="2.1">\n'
' <context>\n'
' <name>NonExistentClass</name>\n'
' <message>\n'
' <location filename="../../src/core/classes.cpp" line="20"/>\n'
' <source>About LMMS</source>\n'
' <translation>À propos de LMMS</translation>\n'
' </message>\n'
'</context>\n'
'</TS>\n')
test.run()
test.expect('Error: data/locale: Class does not exist in source code: NonExistentClass')
test.expect('1 errors')
with ScriptTest(check_strings) as test:
create_file('data/themes/classic/style.css',
'lmms--gui--NonExistentClass {'
'\tcolor: #d1d8e4;\n'
'}')
test.run()
test.expect('Error: data/themes/classic/style.css: Class does not exist in source code: NonExistentClass')
test.expect('1 errors')
with ScriptTest(check_namespace) as test:
# minimal working example
test.run(0) # exitcode 0 - no errors expected
test.expect('0 errors')
create_file('01_OddBraceWithinMacro.cpp', '''
#if HAS_EVEN_BRACES
namespace lmms {}
#endif
namespace lmms {
#if HAS_ODD_BRACE
}
#endif
''')
create_file('02_IncludeInCodeBlock.cpp', '''
#include <good>
extern "C" {
#include "alright.c"
}
namespace lmms {
#include <bad>
}
''')
create_file('03_MacroComments.h', '''
#ifndef HEADER_GUARD_NEEDS_NO_COMMENT
#define HEADER_GUARD_NEEDS_NO_COMMENT
#ifdef HAS_NESTED_NEEDS_COMMENT
#ifdef SHORT_NO_NESTED_NEEDS_NO_COMMENT
namespace lmms {}
#endif
#endif
#ifdef HAS_COMMENT
#ifdef SHORT_NO_NESTED_NEEDS_NO_COMMENT
#else
#endif
#endif // HAS_COMMENT
#endif
''')
create_file('04_NamespaceComments.cpp', '''
namespace lmms {
namespace ShortNamespace {
class WithDeclarationsOnly;
}
namespace LongNamespace {
class WithDefinition {
int x;
};
}
} // namespace lmms
''')
create_file('05_NoHeaderGuard.h', '''
#include <cstdio>
namespace lmms {}
''')
create_file('06_PragmaButNoLmms.h', '''
// should not cause header guard warning
#pragma once
namespace not_lmms {}
''')
create_file('07_MismatchingEndifName.h', '''
#ifndef ABC_H
namespace lmms {
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
}
#endif // XYZ_H
''')
create_file('08_MismatchingNamespaceEndName.h', '''
#ifndef ABC_H
namespace lmms {
{}
} // namespace smml
#endif // ABC_H
''')
create_file('09_NoEndifAfterElseIsOk.cpp', '''
#ifdef XYZ
namespace lmms {
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
}
#else
namespace lmms {
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
}
#endif
''')
test.run()
test.expect('8 errors')
test.expect('''
Error: 01_OddBraceWithinMacro.cpp:7: Expected #endif before }
Error: 02_IncludeInCodeBlock.cpp:7: #include inside a code block
Error: 03_MacroComments.h:8: Missing comment // HAS_NESTED_NEEDS_COMMENT
Error: 04_NamespaceComments.cpp:10: Missing comment // namespace LongNamespace
Error: 05_NoHeaderGuard.h: First statement should be header guard
Error: 06_PragmaButNoLmms.h: File has no namespace lmms
Error: 07_MismatchingEndifName.h:36: Missing comment // ABC_H
Error: 08_MismatchingNamespaceEndName.h:5: Missing comment // namespace lmms
''')
# if we made it until here without an exception, all tests have been passed
print("SUCCESS")

View File

@@ -0,0 +1,841 @@
/*
* ArrayVectorTest.cpp
*
* Copyright (c) 2023 Dominic Clark
*
* This file is part of LMMS - https://lmms.io
*
* 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) any later version.
*
* 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 (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "ArrayVector.h"
#include <QObject>
#include <QtTest/QtTest>
#include <array>
#include <iterator>
using lmms::ArrayVector;
struct ShouldNotConstruct
{
ShouldNotConstruct() { QFAIL("should not construct"); }
};
struct ShouldNotDestruct
{
~ShouldNotDestruct() { QFAIL("should not destruct"); }
};
enum class Construction { Default, Copy, Move, CopyAssign, MoveAssign };
struct Constructible
{
Constructible() : construction{Construction::Default} {}
Constructible(const Constructible&) : construction{Construction::Copy} {}
Constructible(Constructible&&) : construction{Construction::Move} {}
Constructible& operator=(const Constructible&) { construction = Construction::CopyAssign; return *this; }
Constructible& operator=(Constructible&&) { construction = Construction::MoveAssign; return *this; }
Construction construction;
};
struct DestructorCheck
{
~DestructorCheck() { *destructed = true; }
bool* destructed;
};
class ArrayVectorTest : public QObject
{
Q_OBJECT
private slots:
void defaultConstructorTest()
{
// Ensure no elements are constructed
const auto v = ArrayVector<ShouldNotConstruct, 1>();
// Ensure the container is empty
QVERIFY(v.empty());
}
void copyConstructorTest()
{
{
// Ensure all elements are copy constructed
const auto v = ArrayVector<Constructible, 1>{{}};
const auto copy = v;
for (const auto& element : copy) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure corresponding elements are used
const auto v = ArrayVector<int, 5>{1, 2, 3};
const auto copy = v;
const auto expected = std::array{1, 2, 3};
QVERIFY(std::equal(copy.begin(), copy.end(), expected.begin(), expected.end()));
}
}
void moveConstructorTest()
{
{
// Ensure all elements are move constructed
auto v = ArrayVector<Constructible, 1>{{}};
const auto moved = std::move(v);
for (const auto& element : moved) {
QCOMPARE(element.construction, Construction::Move);
}
}
{
// Ensure corresponding elements are used
auto v = ArrayVector<int, 5>{1, 2, 3};
const auto moved = std::move(v);
const auto expected = std::array{1, 2, 3};
QVERIFY(std::equal(moved.begin(), moved.end(), expected.begin(), expected.end()));
// Move construction should leave the source empty
QVERIFY(v.empty());
}
}
void fillValueConstructorTest()
{
// Ensure all elements are copy constructed
const auto v = ArrayVector<Constructible, 2>(1, {});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
// Ensure the container has the correct size
QCOMPARE(v.size(), std::size_t{1});
}
void fillDefaultConstructorTest()
{
// Ensure all elements are copy constructed
const auto v = ArrayVector<Constructible, 2>(1);
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Default);
}
// Ensure the container has the correct size
QCOMPARE(v.size(), std::size_t{1});
}
void rangeConstructorTest()
{
{
// Ensure the elements are copy constructed from normal iterators
const auto data = std::array{Constructible{}};
const auto v = ArrayVector<Constructible, 1>(data.begin(), data.end());
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure the elements are move constructed from move iterators
auto data = std::array{Constructible{}};
const auto v = ArrayVector<Constructible, 1>(
std::move_iterator{data.begin()}, std::move_iterator{data.end()});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Move);
}
}
{
// Ensure corresponding elements are used
const auto data = std::array{1, 2, 3};
const auto v = ArrayVector<int, 5>(data.begin(), data.end());
QVERIFY(std::equal(v.begin(), v.end(), data.begin(), data.end()));
}
}
void initializerListConstructorTest()
{
// Ensure the container is constructed with the correct data
const auto v = ArrayVector<int, 5>{1, 2, 3};
const auto expected = std::array{1, 2, 3};
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
}
void destructorTest()
{
{
// Should not call destructors for space without elements
const auto v = ArrayVector<ShouldNotDestruct, 1>{};
}
{
// Should call destructors for all elements
auto destructed = false;
{
const auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
}
QVERIFY(destructed);
}
}
void copyAssignmentTest()
{
{
// Self-assignment should not change the contents
auto v = ArrayVector<int, 5>{1, 2, 3};
const auto oldValue = v;
v = v;
QCOMPARE(v, oldValue);
}
{
// Assignment to a larger container should copy assign
const auto src = ArrayVector<Constructible, 5>(3);
auto dst = ArrayVector<Constructible, 5>(5);
dst = src;
QCOMPARE(dst.size(), std::size_t{3});
for (const auto& element : dst) {
QCOMPARE(element.construction, Construction::CopyAssign);
}
}
{
// Assignment to a smaller container should copy construct
const auto src = ArrayVector<Constructible, 5>(3);
auto dst = ArrayVector<Constructible, 5>{};
dst = src;
QCOMPARE(dst.size(), std::size_t{3});
for (const auto& element : dst) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure corresponding elements are used
const auto src = ArrayVector<int, 5>{1, 2, 3};
auto dst = ArrayVector<int, 5>{};
dst = src;
QCOMPARE(dst, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void moveAssignmentTest()
{
{
// Self-assignment should not change the contents
//// Please note the following:
//// https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2468
//// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81159
auto v = ArrayVector<int, 5>{1, 2, 3};
const auto oldValue = v;
#if __GNUC__ >= 13
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wself-move"
#endif
v = std::move(v);
#if __GNUC__ >= 13
# pragma GCC diagnostic pop
#endif
QCOMPARE(v, oldValue);
}
{
// Assignment to a larger container should move assign
auto src = ArrayVector<Constructible, 5>(3);
auto dst = ArrayVector<Constructible, 5>(5);
dst = std::move(src);
QCOMPARE(dst.size(), std::size_t{3});
for (const auto& element : dst) {
QCOMPARE(element.construction, Construction::MoveAssign);
}
}
{
// Assignment to a smaller container should move construct
auto src = ArrayVector<Constructible, 5>(3);
auto dst = ArrayVector<Constructible, 5>{};
dst = std::move(src);
QCOMPARE(dst.size(), std::size_t{3});
for (const auto& element : dst) {
QCOMPARE(element.construction, Construction::Move);
}
}
{
// Ensure corresponding elements are used
auto src = ArrayVector<int, 5>{1, 2, 3};
auto dst = ArrayVector<int, 5>{};
dst = std::move(src);
QCOMPARE(dst, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void initializerListAssignmentTest()
{
{
// Assignment to a larger container should copy assign
auto v = ArrayVector<Constructible, 2>(2);
v = {Constructible{}};
QCOMPARE(v.size(), std::size_t{1});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::CopyAssign);
}
}
{
// Assignment to a smaller container should copy construct
auto v = ArrayVector<Constructible, 2>{};
v = {Constructible{}};
QCOMPARE(v.size(), std::size_t{1});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure corresponding elements are used
auto v = ArrayVector<int, 5>{};
v = {1, 2, 3};
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void fillValueAssignTest()
{
{
// Assignment to a larger container should copy assign
auto v = ArrayVector<Constructible, 5>(5);
v.assign(3, {});
QCOMPARE(v.size(), std::size_t{3});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::CopyAssign);
}
}
{
// Assignment to a smaller container should copy construct
auto v = ArrayVector<Constructible, 5>{};
v.assign(3, {});
QCOMPARE(v.size(), std::size_t{3});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure correct value is filled
auto v = ArrayVector<int, 5>{};
v.assign(3, 1);
QCOMPARE(v, (ArrayVector<int, 5>{1, 1, 1}));
}
}
void rangeAssignTest()
{
{
// Assignment to a larger container should copy assign
const auto data = std::array{Constructible{}};
auto v = ArrayVector<Constructible, 2>(2);
v.assign(data.begin(), data.end());
QCOMPARE(v.size(), std::size_t{1});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::CopyAssign);
}
}
{
// Assignment to a smaller container should copy construct
const auto data = std::array{Constructible{}};
auto v = ArrayVector<Constructible, 2>{};
v.assign(data.begin(), data.end());
QCOMPARE(v.size(), std::size_t{1});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure correct value is filled
const auto data = std::array{1, 2, 3};
auto v = ArrayVector<int, 5>{};
v.assign(data.begin(), data.end());
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void atTest()
{
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.at(1), 2);
QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range);
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.at(1), 2);
QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range);
}
}
void subscriptTest()
{
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v[1], 2);
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v[1], 2);
}
}
void frontTest()
{
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.front(), 1);
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.front(), 1);
}
}
void backTest()
{
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.back(), 3);
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.back(), 3);
}
}
void dataTest()
{
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.data(), &v.front());
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QCOMPARE(v.data(), &v.front());
}
}
void beginEndTest()
{
const auto expected = std::array{1, 2, 3};
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
QVERIFY(std::equal(v.cbegin(), v.cend(), expected.begin(), expected.end()));
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
}
}
void rbeginRendTest()
{
const auto expected = std::array{3, 2, 1};
{
// Non-const version
auto v = ArrayVector<int, 5>{1, 2, 3};
QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end()));
QVERIFY(std::equal(v.crbegin(), v.crend(), expected.begin(), expected.end()));
}
{
// Const version
const auto v = ArrayVector<int, 5>{1, 2, 3};
QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end()));
}
}
void emptyFullSizeMaxCapacityTest()
{
auto v = ArrayVector<int, 2>{};
QVERIFY(v.empty());
QVERIFY(!v.full());
QCOMPARE(v.size(), std::size_t{0});
QCOMPARE(v.max_size(), std::size_t{2});
QCOMPARE(v.capacity(), std::size_t{2});
v.push_back(1);
QVERIFY(!v.empty());
QVERIFY(!v.full());
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v.max_size(), std::size_t{2});
QCOMPARE(v.capacity(), std::size_t{2});
v.push_back(2);
QVERIFY(!v.empty());
QVERIFY(v.full());
QCOMPARE(v.size(), std::size_t{2});
QCOMPARE(v.max_size(), std::size_t{2});
QCOMPARE(v.capacity(), std::size_t{2});
auto empty = ArrayVector<int, 0>{};
QVERIFY(empty.empty());
QVERIFY(empty.full());
QCOMPARE(empty.size(), std::size_t{0});
QCOMPARE(empty.max_size(), std::size_t{0});
QCOMPARE(empty.capacity(), std::size_t{0});
}
void insertValueTest()
{
{
// Copy
const auto data = Constructible{};
auto v = ArrayVector<Constructible, 1>{};
v.insert(v.cbegin(), data);
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Copy);
}
{
// Move
auto v = ArrayVector<Constructible, 1>{};
v.insert(v.cbegin(), Constructible{});
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Move);
}
{
// Ensure the correct value is used (copy)
const auto data = 1;
auto v = ArrayVector<int, 5>{2, 3};
v.insert(v.cbegin(), data);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
{
// Ensure the correct value is used (move)
auto v = ArrayVector<int, 5>{2, 3};
v.insert(v.cbegin(), 1);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void insertFillValueTest()
{
{
// Insertion should copy construct
auto v = ArrayVector<Constructible, 5>{};
v.insert(v.cbegin(), 3, {});
QCOMPARE(v.size(), std::size_t{3});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure correct value is filled
auto v = ArrayVector<int, 5>{1, 3};
v.insert(v.cbegin() + 1, 3, 2);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 2, 2, 3}));
}
}
void insertRangeTest()
{
{
// Insertion should copy construct
const auto data = std::array{Constructible{}};
auto v = ArrayVector<Constructible, 2>{};
v.insert(v.cbegin(), data.begin(), data.end());
QCOMPARE(v.size(), std::size_t{1});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure correct value is filled
const auto data = std::array{2, 3};
auto v = ArrayVector<int, 5>{1, 4};
v.insert(v.cbegin() + 1, data.begin(), data.end());
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3, 4}));
}
}
void insertInitializerListTest()
{
{
// Insertion should copy construct
auto v = ArrayVector<Constructible, 2>{};
v.insert(v.cbegin(), {Constructible{}});
QCOMPARE(v.size(), std::size_t{1});
for (const auto& element : v) {
QCOMPARE(element.construction, Construction::Copy);
}
}
{
// Ensure corresponding elements are used
auto v = ArrayVector<int, 5>{1, 4};
v.insert(v.cbegin() + 1, {2, 3});
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3, 4}));
}
}
void emplaceTest()
{
{
// Ensure the value is constructed in-place
auto v = ArrayVector<Constructible, 1>{};
v.emplace(v.cbegin());
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Default);
}
{
// Ensure the correct value is used (move)
auto v = ArrayVector<int, 5>{2, 3};
v.emplace(v.cbegin(), 1);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void eraseTest()
{
{
// Ensure destructors are run
auto destructed = false;
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
v.erase(v.cbegin());
QVERIFY(destructed);
}
{
// Ensure the result is correct
auto v = ArrayVector<int, 5>{10, 1, 2, 3};
v.erase(v.cbegin());
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void eraseRangeTest()
{
{
// Ensure destructors are run
auto destructed = false;
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
v.erase(v.cbegin(), v.cend());
QVERIFY(destructed);
}
{
// Ensure the result is correct
auto v = ArrayVector<int, 5>{1, 20, 21, 2, 3};
v.erase(v.cbegin() + 1, v.cbegin() + 3);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void pushBackTest()
{
{
// Copy
const auto data = Constructible{};
auto v = ArrayVector<Constructible, 1>{};
v.push_back(data);
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Copy);
}
{
// Move
auto v = ArrayVector<Constructible, 1>{};
v.push_back({});
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Move);
}
{
// Ensure the correct value is used (copy)
const auto data = 3;
auto v = ArrayVector<int, 5>{1, 2};
v.push_back(data);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
{
// Ensure the correct value is used (move)
auto v = ArrayVector<int, 5>{1, 2};
v.push_back(3);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void emplaceBackTest()
{
{
// Ensure the value is constructed in-place
auto v = ArrayVector<Constructible, 1>{};
v.emplace_back();
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Default);
}
{
// Ensure the correct value is used (move)
auto v = ArrayVector<int, 5>{1, 2};
v.emplace_back(3);
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
}
}
void popBackTest()
{
{
// Ensure destructors are run
auto destructed = false;
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
v.pop_back();
QVERIFY(destructed);
}
{
// Ensure the result is correct
auto v = ArrayVector<int, 5>{1, 2, 3};
v.pop_back();
QCOMPARE(v, (ArrayVector<int, 5>{1, 2}));
}
}
void resizeDefaultTest()
{
{
// Smaller
auto destructed = false;
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
QCOMPARE(v.size(), std::size_t{1});
v.resize(0);
QCOMPARE(v.size(), std::size_t{0});
QVERIFY(destructed);
}
{
// Bigger
auto v = ArrayVector<Constructible, 1>{};
QCOMPARE(v.size(), std::size_t{0});
v.resize(1);
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Default);
}
{
// Too big
auto v = ArrayVector<int, 1>{};
QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error);
}
}
void resizeValueTest()
{
{
// Smaller
auto dummy = false;
auto destructed = false;
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
QCOMPARE(v.size(), std::size_t{1});
v.resize(0, {&dummy});
QCOMPARE(v.size(), std::size_t{0});
QVERIFY(destructed);
}
{
// Bigger
auto v = ArrayVector<Constructible, 1>{};
QCOMPARE(v.size(), std::size_t{0});
v.resize(1, {});
QCOMPARE(v.size(), std::size_t{1});
QCOMPARE(v[0].construction, Construction::Copy);
}
{
// Too big
auto v = ArrayVector<int, 1>{};
QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error);
}
{
// Ensure the correct value is used
auto v = ArrayVector<int, 1>{};
v.resize(1, 1);
QCOMPARE(v, (ArrayVector<int, 1>{1}));
}
}
void clearTest()
{
{
// Ensure destructors are run
auto destructed = false;
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
v.clear();
QVERIFY(destructed);
}
{
// Ensure the result is correct
auto v = ArrayVector<int, 5>{1, 2, 3};
v.clear();
QCOMPARE(v, (ArrayVector<int, 5>{}));
}
}
void memberSwapTest()
{
auto a = ArrayVector<int, 5>{1, 2, 3, 4};
auto b = ArrayVector<int, 5>{2, 4, 6};
const auto aOriginal = a;
const auto bOriginal = b;
a.swap(b);
QCOMPARE(a, bOriginal);
QCOMPARE(b, aOriginal);
}
void freeSwapTest()
{
auto a = ArrayVector<int, 5>{1, 2, 3, 4};
auto b = ArrayVector<int, 5>{2, 4, 6};
const auto aOriginal = a;
const auto bOriginal = b;
swap(a, b);
QCOMPARE(a, bOriginal);
QCOMPARE(b, aOriginal);
}
void comparisonTest()
{
const auto v = ArrayVector<int, 5>{1, 2, 3};
const auto l = ArrayVector<int, 5>{1, 2, 2};
const auto e = ArrayVector<int, 5>{1, 2, 3};
const auto g = ArrayVector<int, 5>{1, 3, 3};
QVERIFY(l < v);
QVERIFY(!(e < v));
QVERIFY(!(g < v));
QVERIFY(l <= v);
QVERIFY(e <= v);
QVERIFY(!(g <= v));
QVERIFY(!(l > v));
QVERIFY(!(e > v));
QVERIFY(g > v);
QVERIFY(!(l >= v));
QVERIFY(e >= v);
QVERIFY(g >= v);
QVERIFY(!(l == v));
QVERIFY(e == v);
QVERIFY(!(g == v));
QVERIFY(l != v);
QVERIFY(!(e != v));
QVERIFY(g != v);
}
};
QTEST_GUILESS_MAIN(ArrayVectorTest)
#include "ArrayVectorTest.moc"

View File

@@ -22,15 +22,16 @@
*
*/
#include "QTestSuite.h"
#include <QtTest/QtTest>
#include "AutomatableModel.h"
#include "ComboBoxModel.h"
#include "Engine.h"
class AutomatableModelTest : QTestSuite
class AutomatableModelTest : public QObject
{
Q_OBJECT
public:
bool m1Changed, m2Changed;
void resetChanged() { m1Changed = m2Changed = false; }
@@ -41,8 +42,23 @@ private slots: // helper slots
private slots: // tests
//! Test that upcast and exact casts work,
//! but no downcast or any other casts
void initTestCase()
{
using namespace lmms;
Engine::init(true);
}
void cleanupTestCase()
{
using namespace lmms;
Engine::destroy();
}
void CastTests()
{
using namespace lmms;
ComboBoxModel comboModel;
AutomatableModel* amPtr = &comboModel;
QVERIFY(nullptr == amPtr->dynamicCast<FloatModel>()); // not a parent class
@@ -60,6 +76,8 @@ private slots: // tests
void LinkTests()
{
using namespace lmms;
BoolModel m1(false), m2(false);
QObject::connect(&m1, SIGNAL(dataChanged()),
@@ -96,6 +114,7 @@ private slots: // tests
QVERIFY(m2.value());
QVERIFY(!m3.value());
}
} AutomatableModelTests;
};
QTEST_GUILESS_MAIN(AutomatableModelTest)
#include "AutomatableModelTest.moc"

View File

@@ -0,0 +1,54 @@
/*
* MathTest.cpp
*
* Copyright (c) 2023 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
*
* This file is part of LMMS - https://lmms.io
*
* 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) any later version.
*
* 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 (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QDir>
#include <QObject>
#include <QtTest/QtTest>
#include "lmms_math.h"
class MathTest : public QObject
{
Q_OBJECT
private slots:
void NumDigitsTest()
{
using namespace lmms;
QCOMPARE(numDigitsAsInt(1.f), 1);
QCOMPARE(numDigitsAsInt(9.9f), 2);
QCOMPARE(numDigitsAsInt(10.f), 2);
QCOMPARE(numDigitsAsInt(0.f), 1);
QCOMPARE(numDigitsAsInt(-100.f), 4);
QCOMPARE(numDigitsAsInt(-99.f), 3);
QCOMPARE(numDigitsAsInt(-0.4f), 1); // there is no "-0" for LED spinbox
QCOMPARE(numDigitsAsInt(-0.99f), 2);
QCOMPARE(numDigitsAsInt(1000000000), 10);
QCOMPARE(numDigitsAsInt(-1000000000), 11);
QCOMPARE(numDigitsAsInt(900000000), 9);
QCOMPARE(numDigitsAsInt(-900000000), 10);
}
};
QTEST_GUILESS_MAIN(MathTest)
#include "MathTest.moc"

View File

@@ -22,14 +22,15 @@
*
*/
#include "QTestSuite.h"
#include <QtTest/QtTest>
#include "MemoryPool.h"
#include <array>
#include <stack>
class MemoryPoolTest : QTestSuite
using namespace lmms;
class MemoryPoolTest : public QObject
{
Q_OBJECT
private slots:
@@ -54,6 +55,8 @@ private slots:
ptrs.pop();
}
}
} MemoryPoolTests;
};
QTEST_GUILESS_MAIN(MemoryPoolTest)
#include "MemoryPoolTest.moc"

View File

@@ -22,23 +22,25 @@
*
*/
#include "QTestSuite.h"
#include "ProjectVersion.h"
class ProjectVersionTest : QTestSuite
#include <QtTest/QtTest>
class ProjectVersionTest : public QObject
{
Q_OBJECT
private slots:
void ProjectVersionComparisonTests()
{
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Minor) > "1.0.3");
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Major) < "2.1.0");
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Release) > "0.2.1");
QVERIFY(ProjectVersion("1.1.4", ProjectVersion::Release) < "1.1.10");
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Minor) == "1.1.5");
QVERIFY( ! ( ProjectVersion("3.1.0", ProjectVersion::Minor) < "2.2.5" ) );
QVERIFY( ! ( ProjectVersion("2.5.0", ProjectVersion::Release) < "2.2.5" ) );
using namespace lmms;
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Minor) > "1.0.3");
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Major) < "2.1.0");
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Release) > "0.2.1");
QVERIFY(ProjectVersion("1.1.4", ProjectVersion::CompareType::Release) < "1.1.10");
QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Minor) == "1.1.5");
QVERIFY( ! ( ProjectVersion("3.1.0", ProjectVersion::CompareType::Minor) < "2.2.5" ) );
QVERIFY( ! ( ProjectVersion("2.5.0", ProjectVersion::CompareType::Release) < "2.2.5" ) );
//A pre-release version has lower precedence than a normal version
QVERIFY(ProjectVersion("1.1.0") > "1.1.0-alpha");
//But higher precedence than the previous version
@@ -60,7 +62,7 @@ private slots:
QVERIFY(ProjectVersion("1.2.3.42") == "1.2.3");
//CompareVersion "All" should compare every identifier
QVERIFY(
ProjectVersion("1.0.0-a.b.c.d.e.f.g.h.i.j.k.l", ProjectVersion::All)
ProjectVersion("1.0.0-a.b.c.d.e.f.g.h.i.j.k.l", ProjectVersion::CompareType::All)
< "1.0.0-a.b.c.d.e.f.g.h.i.j.k.m"
);
//Prerelease identifiers may contain hyphens
@@ -73,6 +75,7 @@ private slots:
//An identifier of the form "-x" is non-numeric, not negative
QVERIFY(ProjectVersion("1.0.0-alpha.-1") > "1.0.0-alpha.1");
}
} ProjectVersionTests;
};
QTEST_GUILESS_MAIN(ProjectVersionTest)
#include "ProjectVersionTest.moc"

View File

@@ -22,20 +22,22 @@
*
*/
#include "QTestSuite.h"
#include <QDir>
#include <QObject>
#include <QtTest/QtTest>
#include "ConfigManager.h"
#include "SampleBuffer.h"
#include "PathUtil.h"
#include "SampleBuffer.h"
#include <QDir>
class RelativePathsTest : QTestSuite
class RelativePathsTest : public QObject
{
Q_OBJECT
private slots:
void PathUtilComparisonTests()
{
using namespace lmms;
QFileInfo fi(ConfigManager::inst()->factorySamplesDir() + "/drums/kick01.ogg");
QVERIFY(fi.exists());
@@ -64,6 +66,7 @@ private slots:
QCOMPARE(PathUtil::toAbsolute(""), empty);
QCOMPARE(PathUtil::toShortestRelative(""), empty);
}
} RelativePathTests;
};
QTEST_GUILESS_MAIN(RelativePathsTest)
#include "RelativePathsTest.moc"

View File

@@ -22,83 +22,98 @@
*
*/
#include "QTestSuite.h"
#include <QtTest/QtTest>
#include "QCoreApplication"
#include "AutomationPattern.h"
#include "AutomationClip.h"
#include "AutomationTrack.h"
#include "BBTrack.h"
#include "BBTrackContainer.h"
#include "DetuningHelper.h"
#include "InstrumentTrack.h"
#include "Pattern.h"
#include "MidiClip.h"
#include "PatternClip.h"
#include "PatternTrack.h"
#include "PatternStore.h"
#include "TrackContainer.h"
#include "Engine.h"
#include "Song.h"
class AutomationTrackTest : QTestSuite
class AutomationTrackTest : public QObject
{
Q_OBJECT
private slots:
void initTestCase()
{
using namespace lmms;
Engine::init(true);
}
void testPatternLinear()
void cleanupTestCase()
{
AutomationPattern p(nullptr);
p.setProgressionType(AutomationPattern::LinearProgression);
p.putValue(0, 0.0, false);
p.putValue(100, 1.0, false);
QCOMPARE(p.valueAt(0), 0.0f);
QCOMPARE(p.valueAt(25), 0.25f);
QCOMPARE(p.valueAt(50), 0.5f);
QCOMPARE(p.valueAt(75), 0.75f);
QCOMPARE(p.valueAt(100), 1.0f);
QCOMPARE(p.valueAt(150), 1.0f);
using namespace lmms;
Engine::destroy();
}
void testPatternDiscrete()
void testClipLinear()
{
AutomationPattern p(nullptr);
p.setProgressionType(AutomationPattern::DiscreteProgression);
p.putValue(0, 0.0, false);
p.putValue(100, 1.0, false);
using namespace lmms;
QCOMPARE(p.valueAt(0), 0.0f);
QCOMPARE(p.valueAt(50), 0.0f);
QCOMPARE(p.valueAt(100), 1.0f);
QCOMPARE(p.valueAt(150), 1.0f);
AutomationClip c(nullptr);
c.setProgressionType(AutomationClip::ProgressionType::Linear);
c.putValue(0, 0.0, false);
c.putValue(100, 1.0, false);
QCOMPARE(c.valueAt(0), 0.0f);
QCOMPARE(c.valueAt(25), 0.25f);
QCOMPARE(c.valueAt(50), 0.5f);
QCOMPARE(c.valueAt(75), 0.75f);
QCOMPARE(c.valueAt(100), 1.0f);
QCOMPARE(c.valueAt(150), 1.0f);
}
void testPatterns()
void testClipDiscrete()
{
using namespace lmms;
AutomationClip c(nullptr);
c.setProgressionType(AutomationClip::ProgressionType::Discrete);
c.putValue(0, 0.0, false);
c.putValue(100, 1.0, false);
QCOMPARE(c.valueAt(0), 0.0f);
QCOMPARE(c.valueAt(50), 0.0f);
QCOMPARE(c.valueAt(100), 1.0f);
QCOMPARE(c.valueAt(150), 1.0f);
}
void testClips()
{
using namespace lmms;
FloatModel model;
auto song = Engine::getSong();
AutomationTrack track(song);
AutomationPattern p1(&track);
p1.setProgressionType(AutomationPattern::LinearProgression);
p1.putValue(0, 0.0, false);
p1.putValue(10, 1.0, false);
p1.movePosition(0);
p1.addObject(&model);
AutomationClip c1(&track);
c1.setProgressionType(AutomationClip::ProgressionType::Linear);
c1.putValue(0, 0.0, false);
c1.putValue(10, 1.0, false);
c1.movePosition(0);
c1.addObject(&model);
AutomationPattern p2(&track);
p2.setProgressionType(AutomationPattern::LinearProgression);
p2.putValue(0, 0.0, false);
p2.putValue(100, 1.0, false);
p2.movePosition(100);
p2.addObject(&model);
AutomationClip c2(&track);
c2.setProgressionType(AutomationClip::ProgressionType::Linear);
c2.putValue(0, 0.0, false);
c2.putValue(100, 1.0, false);
c2.movePosition(100);
c2.addObject(&model);
AutomationPattern p3(&track);
p3.addObject(&model);
AutomationClip c3(&track);
c3.addObject(&model);
//XXX: Why is this even necessary?
p3.clear();
c3.clear();
QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f);
QCOMPARE(song->automatedValuesAt( 5)[&model], 0.5f);
@@ -110,24 +125,26 @@ private slots:
void testLengthRespected()
{
using namespace lmms;
FloatModel model;
auto song = Engine::getSong();
AutomationTrack track(song);
AutomationPattern p(&track);
p.setProgressionType(AutomationPattern::LinearProgression);
p.addObject(&model);
AutomationClip c(&track);
c.setProgressionType(AutomationClip::ProgressionType::Linear);
c.addObject(&model);
p.putValue(0, 0.0, false);
p.putValue(100, 1.0, false);
c.putValue(0, 0.0, false);
c.putValue(100, 1.0, false);
p.changeLength(100);
c.changeLength(100);
QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f);
QCOMPARE(song->automatedValuesAt( 50)[&model], 0.5f);
QCOMPARE(song->automatedValuesAt(100)[&model], 1.0f);
p.changeLength(50);
c.changeLength(50);
QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f);
QCOMPARE(song->automatedValuesAt( 50)[&model], 0.5f);
QCOMPARE(song->automatedValuesAt(100)[&model], 0.5f);
@@ -135,59 +152,63 @@ private slots:
void testInlineAutomation()
{
using namespace lmms;
auto song = Engine::getSong();
InstrumentTrack* instrumentTrack =
dynamic_cast<InstrumentTrack*>(Track::create(Track::InstrumentTrack, song));
InstrumentTrack instrumentTrack(song);
Pattern* notePattern = dynamic_cast<Pattern*>(instrumentTrack->createTCO(0));
notePattern->changeLength(TimePos(4, 0));
Note* note = notePattern->addNote(Note(TimePos(4, 0)), false);
MidiClip midiClip(&instrumentTrack);
midiClip.changeLength(TimePos(4, 0));
Note* note = midiClip.addNote(Note(TimePos(4, 0)), false);
note->createDetuning();
DetuningHelper* dh = note->detuning();
auto pattern = dh->automationPattern();
pattern->setProgressionType( AutomationPattern::LinearProgression );
pattern->putValue(TimePos(0, 0), 0.0);
pattern->putValue(TimePos(4, 0), 1.0);
auto clip = dh->automationClip();
clip->setProgressionType( AutomationClip::ProgressionType::Linear );
clip->putValue(TimePos(0, 0), 0.0);
clip->putValue(TimePos(4, 0), 1.0);
QCOMPARE(pattern->valueAt(TimePos(0, 0)), 0.0f);
QCOMPARE(pattern->valueAt(TimePos(1, 0)), 0.25f);
QCOMPARE(pattern->valueAt(TimePos(2, 0)), 0.5f);
QCOMPARE(pattern->valueAt(TimePos(4, 0)), 1.0f);
QCOMPARE(clip->valueAt(TimePos(0, 0)), 0.0f);
QCOMPARE(clip->valueAt(TimePos(1, 0)), 0.25f);
QCOMPARE(clip->valueAt(TimePos(2, 0)), 0.5f);
QCOMPARE(clip->valueAt(TimePos(4, 0)), 1.0f);
}
void testBBTrack()
void testPatternTrack()
{
auto song = Engine::getSong();
auto bbContainer = Engine::getBBTrackContainer();
BBTrack bbTrack(song);
Track* automationTrack = Track::create(Track::AutomationTrack, bbContainer);
using namespace lmms;
QVERIFY(automationTrack->numOfTCOs());
AutomationPattern* p1 = dynamic_cast<AutomationPattern*>(automationTrack->getTCO(0));
QVERIFY(p1);
auto song = Engine::getSong();
auto patternStore = Engine::patternStore();
PatternTrack patternTrack(song);
AutomationTrack automationTrack(patternStore);
automationTrack.createClipsForPattern(patternStore->numOfPatterns() - 1);
QVERIFY(automationTrack.numOfClips());
auto c1 = dynamic_cast<AutomationClip*>(automationTrack.getClip(0));
QVERIFY(c1);
FloatModel model;
p1->setProgressionType(AutomationPattern::LinearProgression);
p1->putValue(0, 0.0, false);
p1->putValue(10, 1.0, false);
p1->addObject(&model);
c1->setProgressionType(AutomationClip::ProgressionType::Linear);
c1->putValue(0, 0.0, false);
c1->putValue(10, 1.0, false);
c1->addObject(&model);
QCOMPARE(bbContainer->automatedValuesAt( 0, bbTrack.index())[&model], 0.0f);
QCOMPARE(bbContainer->automatedValuesAt( 5, bbTrack.index())[&model], 0.5f);
QCOMPARE(bbContainer->automatedValuesAt(10, bbTrack.index())[&model], 1.0f);
QCOMPARE(bbContainer->automatedValuesAt(50, bbTrack.index())[&model], 1.0f);
QCOMPARE(patternStore->automatedValuesAt( 0, patternTrack.patternIndex())[&model], 0.0f);
QCOMPARE(patternStore->automatedValuesAt( 5, patternTrack.patternIndex())[&model], 0.5f);
QCOMPARE(patternStore->automatedValuesAt(10, patternTrack.patternIndex())[&model], 1.0f);
QCOMPARE(patternStore->automatedValuesAt(50, patternTrack.patternIndex())[&model], 1.0f);
BBTrack bbTrack2(song);
PatternTrack patternTrack2(song);
QCOMPARE(bbContainer->automatedValuesAt(5, bbTrack.index())[&model], 0.5f);
QVERIFY(! bbContainer->automatedValuesAt(5, bbTrack2.index()).size());
QCOMPARE(patternStore->automatedValuesAt(5, patternTrack.patternIndex())[&model], 0.5f);
QVERIFY(! patternStore->automatedValuesAt(5, patternTrack2.patternIndex()).size());
BBTCO tco(&bbTrack);
tco.changeLength(TimePos::ticksPerBar() * 2);
tco.movePosition(0);
PatternClip clip(&patternTrack);
clip.changeLength(TimePos::ticksPerBar() * 2);
clip.movePosition(0);
QCOMPARE(song->automatedValuesAt(0)[&model], 0.0f);
QCOMPARE(song->automatedValuesAt(5)[&model], 0.5f);
@@ -196,27 +217,30 @@ private slots:
void testGlobalAutomation()
{
using namespace lmms;
// Global automation should not have priority, see https://github.com/LMMS/lmms/issues/4268
// Tests regression caused by 75077f6200a5aee3a5821aae48a3b8466ed8714a
auto song = Engine::getSong();
auto globalTrack = song->globalAutomationTrack();
AutomationPattern globalPattern(globalTrack);
AutomationClip globalClip(globalTrack);
AutomationTrack localTrack(song);
AutomationPattern localPattern(&localTrack);
AutomationClip localClip(&localTrack);
FloatModel model;
globalPattern.setProgressionType(AutomationPattern::DiscreteProgression);
localPattern.setProgressionType(AutomationPattern::DiscreteProgression);
globalPattern.addObject(&model);
localPattern.addObject(&model);
globalPattern.putValue(0, 100.0f, false);
localPattern.putValue(0, 50.0f, false);
globalClip.setProgressionType(AutomationClip::ProgressionType::Discrete);
localClip.setProgressionType(AutomationClip::ProgressionType::Discrete);
globalClip.addObject(&model);
localClip.addObject(&model);
globalClip.putValue(0, 100.0f, false);
localClip.putValue(0, 50.0f, false);
QCOMPARE(song->automatedValuesAt(0)[&model], 50.0f);
}
} AutomationTrackTest;
};
QTEST_GUILESS_MAIN(AutomationTrackTest)
#include "AutomationTrackTest.moc"