Add script tests and script recorder

This commit is contained in:
Dmitri Ovodok 2018-11-12 14:43:45 +02:00
parent d2471e0c35
commit ee78931e10
22 changed files with 1889 additions and 2 deletions

View file

@ -1129,6 +1129,19 @@ void ScoreDiff::editPropertyDiffs()
deleteDiffs(_diffs, abandonedDiffs);
}
//---------------------------------------------------------
// ScoreDiff::equal
//---------------------------------------------------------
bool ScoreDiff::equal() const
{
for (const TextDiff& td : _textDiffs) {
if (td.type != DiffType::EQUAL)
return false;
}
return true;
}
//---------------------------------------------------------
// ScoreDiff::rawDiff
//---------------------------------------------------------

View file

@ -162,6 +162,8 @@ class ScoreDiff {
const Score* score1() const { return _s1; }
const Score* score2() const { return _s2; }
bool equal() const;
QString rawDiff(bool skipEqual = true) const;
QString userDiff() const;
};

View file

@ -57,6 +57,7 @@ QT5_WRAP_UI (ui_headers
importmidi/importmidi_panel.ui
scorecmp/scorecmp_tool.ui
script/script_recorder.ui
debugger/barline.ui
debugger/harmony.ui
@ -426,6 +427,7 @@ add_executable ( ${ExecutableName}
preferenceslistwidget.cpp preferenceslistwidget.h
extension.cpp extension.h
tourhandler.cpp
script/script.cpp script/scriptentry.cpp script/testscript.cpp script/recorderwidget.cpp
${COCOABRIDGE}
${OMR_FILES}

View file

@ -59,6 +59,7 @@
#include "importmidi/importmidi_panel.h"
#include "importmidi/importmidi_operations.h"
#include "scorecmp/scorecmp.h"
#include "script/recorderwidget.h"
#include "libmscore/scorediff.h"
#include "libmscore/chord.h"
#include "libmscore/segment.h"
@ -156,6 +157,7 @@ QString iconPath;
bool converterMode = false;
static bool rawDiffMode = false;
static bool diffMode = false;
static bool scriptTestMode = false;
bool processJob = false;
bool externalIcons = false;
bool pluginMode = false;
@ -1107,6 +1109,17 @@ MuseScore::MuseScore()
}
addDockWidget(Qt::BottomDockWidgetArea, scoreCmpTool);
if (MuseScore::unstable()) {
scriptRecorder = new ScriptRecorderWidget(this, this);
scriptRecorder->setVisible(false);
QAction* a = getAction("toggle-script-recorder");
connect(
scriptRecorder, &ScriptRecorderWidget::visibilityChanged,
a, &QAction::setChecked
);
addDockWidget(Qt::RightDockWidgetArea, scriptRecorder);
}
mainWindow->setStretchFactor(0, 1);
mainWindow->setStretchFactor(1, 0);
mainWindow->setSizes(QList<int>({500, 50}));
@ -1637,6 +1650,13 @@ MuseScore::MuseScore()
menuTools->addAction(getAction("del-empty-measures"));
if (MuseScore::unstable()) {
menuTools->addSeparator();
a = getAction("toggle-script-recorder");
a->setCheckable(true);
menuTools->addAction(a);
}
//---------------------
// Menu Plugins
//---------------------
@ -2915,7 +2935,7 @@ void MuseScore::removeTab(int i)
QString tmpName = score->tmpName();
if (checkDirty(score))
if (!scriptTestMode && checkDirty(score))
return;
if (seq && seq->score() == score) {
seq->stopWait();
@ -2950,6 +2970,35 @@ void MuseScore::removeTab(int i)
update();
}
//---------------------------------------------------------
// runTestScripts
//---------------------------------------------------------
bool MuseScore::runTestScripts(const QStringList& scriptFiles)
{
ScriptContext ctx(this);
bool allPassed = true;
int passed = 0;
int total = 0;
for (const QString& scriptFile : scriptFiles) {
// ensure no scores are open not to interfere with
// the next script initialization process.
while (!scores().empty()) {
closeScore(scores().back());
}
std::unique_ptr<Script> script = Script::fromFile(scriptFile);
const bool pass = script->execute(ctx);
QTextStream(stdout) << "Test " << scriptFile << (pass ? " PASS" : " FAIL") << endl;
++total;
if (pass)
++passed;
else
allPassed = false;
}
QTextStream(stdout) << "Test scripts: total: " << total << ", passed: " << passed << endl;
return allPassed;
}
//---------------------------------------------------------
// loadTranslation
//---------------------------------------------------------
@ -3418,6 +3467,9 @@ static bool processNonGui(const QStringList& argv)
delete s2;
}
if (scriptTestMode)
return mscore->runTestScripts(argv);
return true;
}
@ -5519,6 +5571,11 @@ ScoreTab* MuseScore::createScoreTab()
void MuseScore::cmd(QAction* a, const QString& cmd)
{
#ifdef MSCORE_UNSTABLE
if (scriptRecorder)
scriptRecorder->recordCommand(cmd);
#endif
if (cmd == "instruments") {
editInstrList();
if (mixer)
@ -5692,6 +5749,10 @@ void MuseScore::cmd(QAction* a, const QString& cmd)
showPianoKeyboard(a->isChecked());
else if (cmd == "toggle-scorecmp-tool")
scoreCmpTool->setVisible(a->isChecked());
#ifdef MSCORE_UNSTABLE
else if (cmd == "toggle-script-recorder")
scriptRecorder->setVisible(a->isChecked());
#endif
else if (cmd == "plugin-creator")
showPluginCreator(a);
else if (cmd == "plugin-manager")
@ -6692,6 +6753,7 @@ int main(int argc, char* av[])
parser.addOption(QCommandLineOption({"e", "experimental"}, "Enable experimental features"));
parser.addOption(QCommandLineOption({"c", "config-folder"}, "Override configuration and settings folder", "dir"));
parser.addOption(QCommandLineOption({"t", "test-mode"}, "Set test mode flag for all files")); // this includes --template-mode
parser.addOption(QCommandLineOption("run-test-script", "Run script tests listed in the command line arguments"));
parser.addOption(QCommandLineOption({"M", "midi-operations"}, "Specify MIDI import operations file", "file"));
parser.addOption(QCommandLineOption({"w", "no-webview"}, "No web view in start center"));
parser.addOption(QCommandLineOption({"P", "export-score-parts"}, "Used with '-o <file>.pdf', export score and parts"));
@ -6846,6 +6908,12 @@ int main(int argc, char* av[])
MScore::noGui = true;
diffMode = true;
}
if (parser.isSet("run-test-script")) {
if (rawDiffMode || diffMode)
qFatal("incompatible options");
MScore::noGui = true;
scriptTestMode = true;
}
QStringList argv = parser.positionalArguments();
@ -6874,6 +6942,8 @@ int main(int argc, char* av[])
if (argv.size() != 2)
qFatal("Only two scores are needed for performing a comparison");
}
if (scriptTestMode && argv.empty())
qFatal("Please specify scripts to execute");
if (dataPath.isEmpty())
dataPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
@ -6972,8 +7042,10 @@ int main(int argc, char* av[])
if (!MScore::noGui)
MuseScore::updateUiStyleAndTheme();
else
else {
genIcons(); // in GUI mode generated in updateUiStyleAndTheme()
noSeq = true;
}
// Do not create sequencer and audio drivers if run with '-s'
if (!noSeq) {

View file

@ -97,6 +97,7 @@ class Driver;
class Seq;
class ImportMidiPanel;
class ScoreComparisonTool;
class ScriptRecorderWidget;
class Startcenter;
class HelpBrowser;
class ToolbarEditor;
@ -246,6 +247,9 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
QSplitter* mainWindow;
ScoreComparisonTool* scoreCmpTool { 0 };
#ifdef MSCORE_UNSTABLE
ScriptRecorderWidget* scriptRecorder { nullptr };
#endif
MagBox* mag;
QComboBox* viewModeCombo;
@ -816,6 +820,9 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
bool importExtension(QString path);
bool uninstallExtension(QString extensionId);
Q_INVOKABLE bool isInstalledExtension(QString extensionId);
bool runTestScripts(const QStringList& scripts);
friend class Script;
};
extern MuseScore* mscore;

View file

@ -0,0 +1,155 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "recorderwidget.h"
#include "ui_script_recorder.h"
#include "musescore.h"
namespace Ms {
//---------------------------------------------------------
// ScriptRecorderWidget
//---------------------------------------------------------
ScriptRecorderWidget::ScriptRecorderWidget(MuseScore* mscore, QWidget* parent)
: QDockWidget(parent), _ui(new Ui::ScriptRecorderWidget), _mscore(mscore),
_recorder(mscore), _scriptDir(QDir::homePath())
{
_ui->setupUi(this);
updateDirLabel();
updateActions();
}
//---------------------------------------------------------
// ~ScriptRecorderWidget
//---------------------------------------------------------
ScriptRecorderWidget::~ScriptRecorderWidget()
{
delete _ui;
}
//---------------------------------------------------------
// ScriptRecorderWidget::changeEvent
//---------------------------------------------------------
void ScriptRecorderWidget::changeEvent(QEvent* e)
{
QDockWidget::changeEvent(e);
switch(e->type()) {
case QEvent::LanguageChange:
_ui->retranslateUi(this);
break;
default:
break;
}
}
//---------------------------------------------------------
// ScriptRecorderWidget::scriptFileName
//---------------------------------------------------------
QString ScriptRecorderWidget::scriptFileName() const
{
QDir dir(scriptDir());
return dir.filePath(_ui->scriptNameEdit->text() + ".script");
}
//---------------------------------------------------------
// ScriptRecorderWidget::updateDirLabel
//---------------------------------------------------------
void ScriptRecorderWidget::updateDirLabel()
{
QFontMetrics metrics(_ui->folderLabel->font());
_ui->folderLabel->setText(metrics.elidedText(_scriptDir, Qt::ElideLeft, _ui->folderLabel->width()));
_ui->folderLabel->setToolTip(_scriptDir);
}
//---------------------------------------------------------
// ScriptRecorderWidget::updateActions
//---------------------------------------------------------
void ScriptRecorderWidget::updateActions()
{
QDir dir(scriptDir());
QFileInfo script(scriptFileName());
_ui->recordButton->setEnabled(dir.exists() && !script.baseName().isEmpty() && !script.exists());
_ui->replayButton->setEnabled(script.isFile());
}
//---------------------------------------------------------
// ScriptRecorderWidget::on_changeFolderButton_clicked
//---------------------------------------------------------
void ScriptRecorderWidget::on_changeFolderButton_clicked()
{
QString newDir = QFileDialog::getExistingDirectory(this, "Open scripts directory");
if (!newDir.isEmpty())
_scriptDir = newDir;
updateDirLabel();
updateActions();
}
//---------------------------------------------------------
// ScriptRecorderWidget::on_scriptNameEdit_textEdited
//---------------------------------------------------------
void ScriptRecorderWidget::on_scriptNameEdit_textEdited()
{
updateActions();
}
//---------------------------------------------------------
// ScriptRecorderWidget::on_recordButton_clicked
//---------------------------------------------------------
void ScriptRecorderWidget::on_recordButton_clicked()
{
const bool startRecord = !_recorder.isRecording();
_ui->changeFolderButton->setEnabled(!startRecord);
_ui->scriptNameEdit->setEnabled(!startRecord);
_ui->replayButton->setEnabled(false);
_ui->recordButton->setText(startRecord ? "Stop recording" : "Start recording");
if (startRecord) {
_recorder.setFile(scriptFileName());
_recorder.context().setCwd(scriptDir());
_recorder.setRecording(true);
_recorder.recordInitState();
}
else {
QFileInfo scriptInfo(scriptFileName());
QString scoreName(scriptInfo.baseName() + ".mscx");
_recorder.recordScoreTest(scoreName);
_recorder.close();
}
if (!_recorder.isRecording())
updateActions();
}
//---------------------------------------------------------
// ScriptRecorderWidget::on_replayButton_clicked
//---------------------------------------------------------
void ScriptRecorderWidget::on_replayButton_clicked()
{
auto script = Script::fromFile(scriptFileName());
if (!script)
return;
ScriptContext ctx(_mscore);
ctx.setCwd(scriptDir());
ctx.setStopOnError(false);
script->execute(ctx);
}
}

View file

@ -0,0 +1,61 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#ifndef __SCRIPT_RECORDER_WIDGET_H__
#define __SCRIPT_RECORDER_WIDGET_H__
#include "script.h"
namespace Ui {
class ScriptRecorderWidget;
}
namespace Ms {
class MuseScore;
//---------------------------------------------------------
// ScriptRecorderWidget
//---------------------------------------------------------
class ScriptRecorderWidget : public QDockWidget {
Q_OBJECT
private:
Ui::ScriptRecorderWidget* _ui;
MuseScore* _mscore;
ScriptRecorder _recorder;
QString _scriptDir;
const QString& scriptDir() const { return _scriptDir; }
QString scriptFileName() const;
void updateDirLabel();
void updateActions();
private slots:
void on_changeFolderButton_clicked();
void on_scriptNameEdit_textEdited();
void on_recordButton_clicked();
void on_replayButton_clicked();
protected:
void changeEvent(QEvent* e) override;
public:
explicit ScriptRecorderWidget(MuseScore* mscore, QWidget* parent = nullptr);
~ScriptRecorderWidget();
void recordCommand(const QString& cmd) { _recorder.recordCommand(cmd); }
};
} // namespace Ms
#endif

172
mscore/script/script.cpp Normal file
View file

@ -0,0 +1,172 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "script.h"
#include "musescore.h"
#include "scoreview.h"
#include "scriptentry.h"
#include "testscript.h"
#include "libmscore/score.h"
namespace Ms {
//---------------------------------------------------------
// ScriptContext
//---------------------------------------------------------
ScriptContext::ScriptContext(MuseScore* context)
: _mscore(context), _cwd(QDir::current())
{
}
//---------------------------------------------------------
// ScriptContext::execLog
// Returns logging stream to be used by script entries
// at execution time.
// Current implementation returns a stream writing to
// stdout performing its initialization if necessary.
//---------------------------------------------------------
QTextStream& ScriptContext::execLog()
{
if (!_execLog)
_execLog.reset(new QTextStream(stdout));
return *_execLog;
}
//---------------------------------------------------------
// Script::execute
//---------------------------------------------------------
bool Script::execute(ScriptContext& ctx) const
{
bool success = true;
for (const auto& e : _entries) {
if (e) {
if (!e->execute(ctx)) {
if (ctx.stopOnError())
return false;
success = false;
}
}
}
return success;
}
//---------------------------------------------------------
// Script::execCmd
//---------------------------------------------------------
void Script::execCmd(MuseScore* mscore, QAction* a, const QString& cmd)
{
mscore->cmd(a, cmd);
}
//---------------------------------------------------------
// Script::fromFile
//---------------------------------------------------------
std::unique_ptr<Script> Script::fromFile(QString fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
return nullptr;
QTextStream stream(&file);
QString line;
auto script = std::unique_ptr<Script>(new Script());
while (stream.readLineInto(&line))
script->addFromLine(line);
return script;
}
//---------------------------------------------------------
// Script::writeToFile
//---------------------------------------------------------
void Script::writeToFile(QString fileName) const
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly))
return;
QTextStream stream(&file);
for (const auto& e : _entries) {
if (e)
stream << e->serialize() << '\n';
}
}
//---------------------------------------------------------
// ScriptRecorder::ensureFileOpen
//---------------------------------------------------------
bool ScriptRecorder::ensureFileOpen()
{
if (_file.isOpen())
return true;
if (_file.open(QIODevice::WriteOnly)) {
_stream.setDevice(&_file);
return true;
}
return false;
}
//---------------------------------------------------------
// ScriptRecorder::syncRecord
//---------------------------------------------------------
void ScriptRecorder::syncRecord()
{
if (!_recording)
return;
if (!ensureFileOpen())
return;
for (; _recorded < _script.nentries(); ++_recorded)
_stream << _script.entry(_recorded).serialize() << endl;
}
//---------------------------------------------------------
// ScriptRecorder::recordInitState
//---------------------------------------------------------
void ScriptRecorder::recordInitState()
{
if (_recording)
_script.addEntry(new InitScriptEntry(_ctx));
syncRecord();
}
//---------------------------------------------------------
// ScriptRecorder::recordCommand
//---------------------------------------------------------
void ScriptRecorder::recordCommand(const QString& name)
{
if (_recording)
_script.addCommand(name);
syncRecord();
}
//---------------------------------------------------------
// ScriptRecorder::recordScoreTest
//---------------------------------------------------------
void ScriptRecorder::recordScoreTest(QString scoreName)
{
if (_recording)
_script.addEntry(ScoreTestScriptEntry::fromContext(_ctx, scoreName));
syncRecord();
}
}

121
mscore/script/script.h Normal file
View file

@ -0,0 +1,121 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#ifndef __SCRIPT_H__
#define __SCRIPT_H__
#include "scriptentry.h"
namespace Ms {
class MasterScore;
class MuseScore;
class ScoreView;
//---------------------------------------------------------
// ScriptContext
//---------------------------------------------------------
class ScriptContext {
MuseScore* _mscore;
QDir _cwd; // current working directory
bool _relativePaths = true;
bool _stopOnError = true;
std::unique_ptr<QTextStream> _execLog;
public:
ScriptContext(MuseScore* mscore);
MuseScore* mscore() { return _mscore; }
const MuseScore* mscore() const { return _mscore; }
const QDir& cwd() const { return _cwd; }
void setCwd(QDir dir) { _cwd = dir; }
bool relativePaths() const { return _relativePaths; }
void setRelativePaths(bool rel) { _relativePaths = rel; }
bool stopOnError() const { return _stopOnError; }
void setStopOnError(bool stop) { _stopOnError = stop; }
QString absoluteFilePath(const QString& filePath) const { return _cwd.absoluteFilePath(filePath); }
QString relativeFilePath(const QString& filePath) const { return _cwd.relativeFilePath(filePath); }
QTextStream& execLog(); // logging stream to be used while executing scripts
};
//---------------------------------------------------------
// Script
//---------------------------------------------------------
class Script final {
std::vector<std::unique_ptr<ScriptEntry>> _entries;
// FIXME: do something better?
friend class CommandScriptEntry;
static void execCmd(MuseScore* mscore, QAction* a, const QString& cmd);
public:
const ScriptEntry& entry(int n) const { return *_entries[n]; }
const ScriptEntry& lastEntry() const { return *_entries.back(); }
int nentries() const { return _entries.size(); }
bool empty() const { return _entries.empty(); }
bool execute(ScriptContext& ctx) const;
void clear() { _entries.clear(); }
void addEntry(std::unique_ptr<ScriptEntry>&& e) { _entries.push_back(std::move(e)); }
void addEntry(ScriptEntry* e) { _entries.emplace_back(e); }
void addCommand(const QByteArray& cmd) { _entries.emplace_back(new CommandScriptEntry(cmd)); }
void addCommand(const QString& cmd) { addCommand(cmd.toLatin1()); }
void addFromLine(const QString& line) { _entries.push_back(ScriptEntry::deserialize(line)); }
static std::unique_ptr<Script> fromFile(QString fileName);
void writeToFile(QString fileName) const;
};
//---------------------------------------------------------
// ScriptRecorder
// Records script writing it to a file just on the fly
// so that the script is preserved in case actions
// series wasn't finished properly (e.g. in case of
// MuseScore crash).
//---------------------------------------------------------
class ScriptRecorder {
QFile _file;
QTextStream _stream;
Script _script;
ScriptContext _ctx;
bool _recording = false;
int _recorded = 0;
bool ensureFileOpen();
void syncRecord();
public:
ScriptRecorder(MuseScore* context) : _ctx(context) {}
bool isRecording() const { return _recording; }
void setRecording(bool r) { _recording = r; }
void close() { setRecording(false); _file.close(); _script.clear(); _recorded = 0; }
void setFile(QString fileName) { close(); _file.setFileName(fileName); }
ScriptContext& context() { return _ctx; }
const ScriptContext& context() const { return _ctx; }
void recordInitState();
void recordCommand(const QString& name);
void recordScoreTest(QString scoreName = QString());
};
} // namespace Ms
#endif

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScriptRecorderWidget</class>
<widget class="QDockWidget" name="ScriptRecorderWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>858</width>
<height>245</height>
</rect>
</property>
<property name="windowTitle">
<string>Script Recorder</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="2">
<widget class="QPushButton" name="changeFolderButton">
<property name="text">
<string>Change folder</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="folderLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Folder:</string>
</property>
<property name="buddy">
<cstring>changeFolderButton</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Script name:</string>
</property>
<property name="buddy">
<cstring>scriptNameEdit</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="recordButton">
<property name="text">
<string>Record</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="replayButton">
<property name="text">
<string>Replay</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="scriptNameEdit"/>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>changeFolderButton</tabstop>
<tabstop>scriptNameEdit</tabstop>
<tabstop>recordButton</tabstop>
<tabstop>replayButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,106 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "scriptentry.h"
#include "musescore.h"
#include "script.h"
#include "testscript.h"
#include "libmscore/score.h"
namespace Ms {
//---------------------------------------------------------
// ScriptEntry::deserialize
//---------------------------------------------------------
std::unique_ptr<ScriptEntry> ScriptEntry::deserialize(const QString& line)
{
QStringList tokens = line.split(' ');
const QString& type = tokens[0];
if (tokens.empty()) {
qWarning("Script line is empty");
return nullptr;
}
if (type == SCRIPT_CMD) {
if (tokens.size() != 2) {
qWarning("Unexpected number of tokens in command: %d", tokens.size());
return nullptr;
}
return std::unique_ptr<ScriptEntry>(new CommandScriptEntry(tokens[1]));
}
if (type == SCRIPT_INIT)
return InitScriptEntry::deserialize(tokens);
if (type == SCRIPT_TEST)
return TestScriptEntry::deserialize(tokens);
qWarning() << "Unsupported action type:" << type;
return nullptr;
}
//---------------------------------------------------------
// InitScriptEntry::InitScriptEntry
//---------------------------------------------------------
InitScriptEntry::InitScriptEntry(const ScriptContext& ctx)
{
MasterScore* score = ctx.mscore()->currentScore()->masterScore();
_filePath = score->importedFilePath();
if (ctx.relativePaths())
_filePath = ctx.relativeFilePath(_filePath);
// TODO: handle excerpts
}
//---------------------------------------------------------
// InitScriptEntry::execute
//---------------------------------------------------------
bool InitScriptEntry::execute(ScriptContext& ctx) const
{
return ctx.mscore()->openScore(ctx.absoluteFilePath(_filePath));
}
//---------------------------------------------------------
// InitScriptEntry::execute
//---------------------------------------------------------
QString InitScriptEntry::serialize() const
{
// TODO: handle spaces in file path!
return entryTemplate(SCRIPT_INIT).arg(_filePath);
}
//---------------------------------------------------------
// InitScriptEntry::deserialize
//---------------------------------------------------------
std::unique_ptr<ScriptEntry> InitScriptEntry::deserialize(const QStringList& tokens)
{
if (tokens.size() != 2) {
qWarning("init: unexpected number of tokens: %d", tokens.size());
return nullptr;
}
return std::unique_ptr<ScriptEntry>(new InitScriptEntry(tokens[1]));
}
//---------------------------------------------------------
// CommandScriptEntry::execute
//---------------------------------------------------------
bool CommandScriptEntry::execute(ScriptContext& ctx) const
{
// ctx.mscore()->cmd(getAction(_command.constData()), _command);
Script::execCmd(ctx.mscore(), getAction(_command.constData()), _command);
return true;
}
}

View file

@ -0,0 +1,69 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#ifndef __SCRIPTENTRY_H__
#define __SCRIPTENTRY_H__
namespace Ms {
class ScriptContext;
//---------------------------------------------------------
// ScriptEntry
//---------------------------------------------------------
class ScriptEntry {
protected:
static constexpr const char* SCRIPT_INIT = "init";
static constexpr const char* SCRIPT_CMD = "cmd";
static constexpr const char* SCRIPT_TEST = "test";
static QString entryTemplate(const char* entryType) { return QString("%1 %2").arg(entryType); }
public:
virtual ~ScriptEntry() = default;
virtual bool execute(ScriptContext& ctx) const = 0;
virtual QString serialize() const = 0;
static std::unique_ptr<ScriptEntry> deserialize(const QString& line);
};
//---------------------------------------------------------
// InitScriptEntry
// Initializes the environment for the script.
//---------------------------------------------------------
class InitScriptEntry : public ScriptEntry {
QString _filePath;
public:
explicit InitScriptEntry(const ScriptContext& ctx);
explicit InitScriptEntry(const QString& filePath) : _filePath(filePath) {}
bool execute(ScriptContext& ctx) const override;
QString serialize() const override;
static std::unique_ptr<ScriptEntry> deserialize(const QStringList& tokens);
};
//---------------------------------------------------------
// CommandScriptEntry
//---------------------------------------------------------
class CommandScriptEntry : public ScriptEntry {
QByteArray _command;
public:
explicit CommandScriptEntry(const QByteArray& cmd) : _command(cmd) {}
explicit CommandScriptEntry(const QString& cmd) : _command(cmd.toLatin1()) {}
explicit CommandScriptEntry(const char* cmd) : _command(cmd) {}
bool execute(ScriptContext& ctx) const override;
QString serialize() const override { return entryTemplate(SCRIPT_CMD).arg(_command.constData()); };
};
} // namespace Ms
#endif

View file

@ -0,0 +1,100 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "testscript.h"
#include "musescore.h"
#include "script.h"
#include "libmscore/scorediff.h"
namespace Ms {
//---------------------------------------------------------
// TestScriptEntry::deserialize
//---------------------------------------------------------
std::unique_ptr<ScriptEntry> TestScriptEntry::deserialize(const QStringList& tokens)
{
// assume that 0th token is just a "test" statement
if (tokens.size() < 2) {
qDebug("test: unexpected tokens size: %d", tokens.size());
return nullptr;
}
const QString& type = tokens[1];
if (type == TEST_SCORE) {
if (tokens.size() != 3) {
qDebug("test: unexpected tokens size: %d", tokens.size());
return nullptr;
}
return std::unique_ptr<ScriptEntry>(new ScoreTestScriptEntry(tokens[2]));
}
qDebug() << "test: unsupported type:" << tokens[1];
return nullptr;
}
//---------------------------------------------------------
// ScoreTestScriptEntry::fromContext
//---------------------------------------------------------
std::unique_ptr<ScriptEntry> ScoreTestScriptEntry::fromContext(const ScriptContext& ctx, QString fileName)
{
MasterScore* score = ctx.mscore()->currentScore()->masterScore();
// TODO: handle excerpts
if (fileName.isEmpty()) {
int scoreNum = 1;
const QString templ("%1.mscx");
fileName = templ.arg(QString::number(scoreNum));
while (QFileInfo(fileName).exists())
fileName = templ.arg(QString::number(++scoreNum));
}
QString filePath = ctx.absoluteFilePath(fileName);
QFileInfo fi(filePath);
score->Score::saveFile(fi);
if (ctx.relativePaths())
filePath = fileName;
return std::unique_ptr<ScriptEntry>(new ScoreTestScriptEntry(filePath));
}
//---------------------------------------------------------
// ScoreTestScriptEntry::execute
//---------------------------------------------------------
bool ScoreTestScriptEntry::execute(ScriptContext& ctx) const
{
MasterScore* curScore = ctx.mscore()->currentScore()->masterScore();
if (!curScore) {
ctx.execLog() << "ScoreTestScriptEntry: no current score" << endl;
return false;
}
QString refFilePath = ctx.absoluteFilePath(_refPath);
std::unique_ptr<MasterScore> refScore(ctx.mscore()->readScore(refFilePath));
if (!refScore) {
ctx.execLog() << "reference score loaded with errors: " << refFilePath << endl;
return false;
}
ScoreDiff diff(curScore, refScore.get(), /* textDiffOnly */ true);
if (!diff.equal()) {
ctx.execLog() << "ScoreTestScriptEntry: fail\n" << diff.rawDiff() << endl;
return false;
}
return true;
}
}

View file

@ -0,0 +1,49 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#ifndef __TESTSCRIPT_H__
#define __TESTSCRIPT_H__
#include "scriptentry.h"
namespace Ms {
class ScriptContext;
//---------------------------------------------------------
// TestScriptEntry
//---------------------------------------------------------
class TestScriptEntry : public ScriptEntry {
protected:
static QString entryTemplate(const char* testType) { return ScriptEntry::entryTemplate(SCRIPT_TEST).arg(testType) + " %1"; }
public:
static constexpr const char* TEST_SCORE = "score";
static std::unique_ptr<ScriptEntry> deserialize(const QStringList& tokens);
};
//---------------------------------------------------------
// ScoreTestScriptEntry
//---------------------------------------------------------
class ScoreTestScriptEntry : public TestScriptEntry {
QString _refPath;
public:
ScoreTestScriptEntry(QString refPath) : _refPath(refPath) {}
bool execute(ScriptContext& ctx) const override;
QString serialize() const override { return entryTemplate(TEST_SCORE).arg(_refPath); }
static std::unique_ptr<ScriptEntry> fromContext(const ScriptContext& ctx, QString fileName = QString());
};
} // namespace Ms
#endif

View file

@ -3583,6 +3583,18 @@ Shortcut Shortcut::_sc[] = {
Icons::mail_ICON,
Qt::ApplicationShortcut
},
#ifdef MSCORE_UNSTABLE
{
MsWidget::MAIN_WINDOW,
STATE_NORMAL,
"toggle-script-recorder",
"Script Recorder",
"Script recorder",
0,
Icons::Invalid_ICON,
Qt::ApplicationShortcut
},
#endif
#ifndef NDEBUG
{
MsWidget::MAIN_WINDOW,

View file

@ -232,6 +232,7 @@ subdirs (
zerberus/opcodeparse
zerberus/inputControls
zerberus/loop
testscript
)

View file

@ -55,6 +55,7 @@ const char* tests[] = {
"importmidi/tst_importmidi",
"libmscore/selectionrangedelete/tst_selectionrangedelete",
"libmscore/parts/tst_parts",
"testscript/tst_runscripts",
#endif
#if 0
"libmscore/spanners/tst_spanners", // FAIL

View file

@ -0,0 +1,18 @@
#=============================================================================
# MuseScore
# Music Composition & Notation
#
# Copyright (C) 2018 Werner Schweer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2
# as published by the Free Software Foundation and appearing in
# the file LICENSE.GPL
#=============================================================================
set(TARGET tst_runscripts)
include(${PROJECT_SOURCE_DIR}/mtest/cmake.inc)
add_dependencies(tst_runscripts mscore)
add_definitions(-DMSCORE_EXECUTABLE="$<TARGET_FILE:mscore>")

View file

@ -0,0 +1,403 @@
<?xml version="1.0" encoding="UTF-8"?>
<museScore version="3.01">
<programVersion>3.0.0</programVersion>
<programRevision>d4d1d69</programRevision>
<Score>
<LayerTag id="0" tag="default"></LayerTag>
<currentLayer>0</currentLayer>
<Synthesizer>
</Synthesizer>
<Division>480</Division>
<Style>
<pageWidth>8.27</pageWidth>
<pageHeight>11.69</pageHeight>
<pagePrintableWidth>7.4826</pagePrintableWidth>
<lastSystemFillLimit>0</lastSystemFillLimit>
<Spatium>1.76389</Spatium>
</Style>
<showInvisible>1</showInvisible>
<showUnprintable>1</showUnprintable>
<showFrames>1</showFrames>
<showMargins>0</showMargins>
<metaTag name="arranger"></metaTag>
<metaTag name="composer"></metaTag>
<metaTag name="copyright"></metaTag>
<metaTag name="creationDate">2018-11-12</metaTag>
<metaTag name="lyricist"></metaTag>
<metaTag name="movementNumber"></metaTag>
<metaTag name="movementTitle"></metaTag>
<metaTag name="platform">Linux</metaTag>
<metaTag name="poet"></metaTag>
<metaTag name="source"></metaTag>
<metaTag name="translator"></metaTag>
<metaTag name="workNumber"></metaTag>
<metaTag name="workTitle">Treble</metaTag>
<Part>
<Staff id="1">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
</Staff>
<trackName>Piano</trackName>
<Instrument>
<longName>Piano</longName>
<shortName>Pno.</shortName>
<trackName>Piano</trackName>
<minPitchP>21</minPitchP>
<maxPitchP>108</maxPitchP>
<minPitchA>21</minPitchA>
<maxPitchA>108</maxPitchA>
<instrumentId>keyboard.piano</instrumentId>
<Articulation>
<velocity>100</velocity>
<gateTime>95</gateTime>
</Articulation>
<Articulation name="staccatissimo">
<velocity>100</velocity>
<gateTime>33</gateTime>
</Articulation>
<Articulation name="staccato">
<velocity>100</velocity>
<gateTime>50</gateTime>
</Articulation>
<Articulation name="portato">
<velocity>100</velocity>
<gateTime>67</gateTime>
</Articulation>
<Articulation name="tenuto">
<velocity>100</velocity>
<gateTime>100</gateTime>
</Articulation>
<Articulation name="marcato">
<velocity>120</velocity>
<gateTime>67</gateTime>
</Articulation>
<Articulation name="sforzato">
<velocity>120</velocity>
<gateTime>100</gateTime>
</Articulation>
<Channel>
<program value="0"/>
<synti>Fluid</synti>
</Channel>
</Instrument>
</Part>
<Staff id="1">
<VBox>
<height>10</height>
<Text>
<style>Title</style>
<text>Treble</text>
</Text>
</VBox>
<Measure>
<voice>
<TimeSig>
<sigN>4</sigN>
<sigD>4</sigD>
</TimeSig>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>69</pitch>
<tpc>17</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>71</pitch>
<tpc>19</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>72</pitch>
<tpc>14</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>74</pitch>
<tpc>16</tpc>
</Note>
</Chord>
</voice>
</Measure>
<Measure>
<voice>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>76</pitch>
<tpc>18</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>77</pitch>
<tpc>13</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>79</pitch>
<tpc>15</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>81</pitch>
<tpc>17</tpc>
</Note>
</Chord>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
</Staff>
</Score>
</museScore>

View file

@ -0,0 +1,12 @@
init init/Treble.mscx
cmd note-input
cmd note-a
cmd note-b
cmd note-c
cmd note-d
cmd note-e
cmd note-f
cmd note-g
cmd note-a
cmd escape
test score basic_note_input.mscx

View file

@ -0,0 +1,355 @@
<?xml version="1.0" encoding="UTF-8"?>
<museScore version="3.01">
<programVersion>3.0.0</programVersion>
<programRevision>d4d1d69</programRevision>
<Score>
<LayerTag id="0" tag="default"></LayerTag>
<currentLayer>0</currentLayer>
<Synthesizer>
</Synthesizer>
<Division>480</Division>
<Style>
<pageWidth>8.27</pageWidth>
<pageHeight>11.69</pageHeight>
<pagePrintableWidth>7.4826</pagePrintableWidth>
<lastSystemFillLimit>0</lastSystemFillLimit>
<Spatium>1.76389</Spatium>
</Style>
<showInvisible>1</showInvisible>
<showUnprintable>1</showUnprintable>
<showFrames>1</showFrames>
<showMargins>0</showMargins>
<metaTag name="arranger"></metaTag>
<metaTag name="composer"></metaTag>
<metaTag name="copyright"></metaTag>
<metaTag name="creationDate">2018-11-12</metaTag>
<metaTag name="lyricist"></metaTag>
<metaTag name="movementNumber"></metaTag>
<metaTag name="movementTitle"></metaTag>
<metaTag name="platform">Linux</metaTag>
<metaTag name="poet"></metaTag>
<metaTag name="source"></metaTag>
<metaTag name="translator"></metaTag>
<metaTag name="workNumber"></metaTag>
<metaTag name="workTitle">Treble</metaTag>
<Part>
<Staff id="1">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
</Staff>
<trackName>Piano</trackName>
<Instrument>
<longName>Piano</longName>
<shortName>Pno.</shortName>
<trackName>Piano</trackName>
<minPitchP>21</minPitchP>
<maxPitchP>108</maxPitchP>
<minPitchA>21</minPitchA>
<maxPitchA>108</maxPitchA>
<instrumentId>keyboard.piano</instrumentId>
<Articulation>
<velocity>100</velocity>
<gateTime>95</gateTime>
</Articulation>
<Articulation name="staccatissimo">
<velocity>100</velocity>
<gateTime>33</gateTime>
</Articulation>
<Articulation name="staccato">
<velocity>100</velocity>
<gateTime>50</gateTime>
</Articulation>
<Articulation name="portato">
<velocity>100</velocity>
<gateTime>67</gateTime>
</Articulation>
<Articulation name="tenuto">
<velocity>100</velocity>
<gateTime>100</gateTime>
</Articulation>
<Articulation name="marcato">
<velocity>120</velocity>
<gateTime>67</gateTime>
</Articulation>
<Articulation name="sforzato">
<velocity>120</velocity>
<gateTime>100</gateTime>
</Articulation>
<Channel>
<program value="0"/>
<synti>Fluid</synti>
</Channel>
</Instrument>
</Part>
<Staff id="1">
<VBox>
<height>10</height>
<Text>
<style>Title</style>
<text>Treble</text>
</Text>
</VBox>
<Measure>
<voice>
<TimeSig>
<sigN>4</sigN>
<sigD>4</sigD>
</TimeSig>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
</Staff>
</Score>
</museScore>

View file

@ -0,0 +1,70 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include <QtTest/QtTest>
#include "mtest/testutils.h"
#include "mscore/script/script.h"
#define DIR QString("testscript/")
using namespace Ms;
//---------------------------------------------------------
// TestScripts
//---------------------------------------------------------
class TestScripts : public QObject, public MTest
{
Q_OBJECT
QString scriptsPath;
private slots:
void initTestCase();
void runTestScripts();
};
//---------------------------------------------------------
// initTestCase
//---------------------------------------------------------
void TestScripts::initTestCase()
{
initMTest();
scriptsPath = root + '/' + DIR + "scripts";
}
//---------------------------------------------------------
// runTestScripts
//---------------------------------------------------------
void TestScripts::runTestScripts()
{
Q_ASSERT(QDir::setCurrent(scriptsPath));
QDir cwd = QDir::current();
QStringList nameFilters({ "*.script" });
cwd.setNameFilters(nameFilters);
cwd.setFilter(QDir::Files);
cwd.setSorting(QDir::Name);
QStringList scripts = cwd.entryList();
QStringList args({ "--run-test-script" });
args << scripts;
if (!QFileInfo(MSCORE_EXECUTABLE).exists())
qFatal("Cannot find executable: %s", MSCORE_EXECUTABLE);
QVERIFY(QProcess::execute(MSCORE_EXECUTABLE, args) == 0);
}
QTEST_MAIN(TestScripts)
#include "tst_runscripts.moc"