Squish: Implement create new test case

Change-Id: I8eeef2d024d6c8b71e2c2482f7da05b9ff221ed9
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Stenger
2022-09-26 15:08:23 +02:00
parent 420386195f
commit 9abecfce01
10 changed files with 301 additions and 9 deletions

View File

@@ -193,13 +193,17 @@ void SquishFileHandler::openTestSuites()
emit suitesOpened();
}
void SquishFileHandler::openTestSuite(const Utils::FilePath &suitePath)
void SquishFileHandler::openTestSuite(const Utils::FilePath &suitePath, bool isReopen)
{
const QString suiteName = suitePath.parentDir().fileName();
const QString suitePathStr = suitePath.toString();
const QStringList cases = SuiteConf::validTestCases(suitePath.parentDir().toString());
if (m_suites.contains(suiteName)) {
if (isReopen) {
modifySuiteItem(suiteName, suitePathStr, cases);
return;
}
QMessageBox::Button replaceSuite
= QMessageBox::question(Core::ICore::dialogParent(),
Tr::tr("Suite Already Open"),

View File

@@ -22,7 +22,7 @@ public:
~SquishFileHandler() override = default;
static SquishFileHandler *instance();
void openTestSuites();
void openTestSuite(const Utils::FilePath &suitePath);
void openTestSuite(const Utils::FilePath &suitePath, bool isReopen = false);
void closeTestSuite(const QString &suiteName);
void closeAllTestSuites();
void runTestCase(const QString &suiteName, const QString &testCaseName);

View File

@@ -5,6 +5,8 @@
#include "squishconstants.h"
#include "squishfilehandler.h"
#include "squishplugin.h"
#include "squishsettings.h"
#include "squishtesttreemodel.h"
#include "squishtesttreeview.h"
#include "squishtr.h"
@@ -50,6 +52,7 @@ SquishNavigationWidget::SquishNavigationWidget(QWidget *parent)
header->setSectionResizeMode(2, QHeaderView::Fixed);
m_view->setHeader(header);
m_view->setHeaderHidden(true);
m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
@@ -132,6 +135,10 @@ void SquishNavigationWidget::contextMenuEvent(QContextMenuEvent *event)
connect(runThisTestSuite, &QAction::triggered, [suiteName]() {
SquishFileHandler::instance()->runTestSuite(suiteName);
});
connect(addNewTestCase, &QAction::triggered, [this, idx]() {
onNewTestCaseTriggered(idx);
});
connect(closeTestSuite, &QAction::triggered, [suiteName]() {
SquishFileHandler::instance()->closeTestSuite(suiteName);
});
@@ -320,6 +327,33 @@ void SquishNavigationWidget::onRecordTestCase(const QString &suiteName, const QS
SquishFileHandler::instance()->recordTestCase(suiteName, testCase);
}
void SquishNavigationWidget::onNewTestCaseTriggered(const QModelIndex &index)
{
auto settings = SquishPlugin::squishSettings();
QTC_ASSERT(settings, return);
if (!settings->squishPath.filePath().pathAppended("scriptmodules").exists()) {
QMessageBox::critical(Core::ICore::dialogParent(),
Tr::tr("Error"),
Tr::tr("Set up a valid Squish path to be able to create "
"a new test case.\n(Edit > Preferences > Squish)"));
return;
}
SquishTestTreeItem *suiteItem = m_model->itemForIndex(m_sortModel->mapToSource(index));
QTC_ASSERT(suiteItem, return);
const QString name = suiteItem->generateTestCaseName();
SquishTestTreeItem *item = new SquishTestTreeItem(name, SquishTestTreeItem::SquishTestCase);
item->setParentName(suiteItem->displayName());
m_model->addTreeItem(item);
m_view->expand(index);
QModelIndex added = m_model->indexForItem(item);
QTC_ASSERT(added.isValid(), return);
m_view->edit(m_sortModel->mapFromSource(added));
}
SquishNavigationWidgetFactory::SquishNavigationWidgetFactory()
{
setDisplayName(Tr::tr("Squish"));

View File

@@ -36,6 +36,8 @@ private:
void onRemoveSharedFolderTriggered(int row, const QModelIndex &parent);
void onRemoveAllSharedFolderTriggered();
void onRecordTestCase(const QString &suiteName, const QString &testCase);
void onNewTestCaseTriggered(const QModelIndex &index);
SquishTestTreeView *m_view;
SquishTestTreeModel *m_model; // not owned
SquishTestTreeSortModel *m_sortModel;

View File

@@ -5,8 +5,10 @@
#include "squishfilehandler.h"
#include "squishtr.h"
#include "suiteconf.h"
#include <debugger/debuggericons.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
@@ -23,23 +25,23 @@ SquishTestTreeItem::SquishTestTreeItem(const QString &displayName, Type type)
, m_type(type)
, m_checked(Qt::Checked)
{
const Qt::ItemFlags common = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
switch (type) {
case Root:
m_flags = Qt::NoItemFlags;
break;
case SquishSuite:
m_flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserTristate
| Qt::ItemIsUserCheckable;
m_flags = common | Qt::ItemIsUserTristate | Qt::ItemIsUserCheckable;
break;
case SquishTestCase:
m_flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
m_flags = common | Qt::ItemIsEditable | Qt::ItemIsUserCheckable;
break;
case SquishSharedData:
case SquishSharedDataFolder:
case SquishSharedFile:
case SquishSharedFolder:
case SquishSharedRoot:
m_flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
m_flags = common;
break;
}
}
@@ -108,9 +110,41 @@ bool SquishTestTreeItem::modifyContent(const SquishTestTreeItem &other)
m_displayName = other.m_displayName;
m_filePath = other.m_filePath;
m_parentName = other.m_parentName;
removeChildren();
if (other.hasChildren()) {
for (int i = 0; i < other.childCount(); ++i) {
const auto modifiedChild = static_cast<SquishTestTreeItem *>(other.childAt(i));
auto child = new SquishTestTreeItem(modifiedChild->displayName(),
modifiedChild->type());
child->modifyContent(*modifiedChild);
appendChild(child);
}
}
return modified;
}
QString SquishTestTreeItem::generateTestCaseName() const
{
QTC_ASSERT(m_type == SquishSuite, return {});
const auto suiteConfFilePath = Utils::FilePath::fromString(m_filePath);
const SuiteConf suiteConf = SuiteConf::readSuiteConf(suiteConfFilePath);
const QStringList used = suiteConf.usedTestCases();
const auto suiteDir = suiteConfFilePath.parentDir();
const QString tmpl("tst_case");
for (int i = 1; i < 9999; ++i) {
const QString current = tmpl + QString::number(i);
if (used.contains(current))
continue;
const Utils::FilePath testCaseFolder = suiteDir.pathAppended(current);
if (!testCaseFolder.exists())
return current;
}
return {};
}
void SquishTestTreeItem::revalidateCheckState()
{
if (childCount() == 0)
@@ -363,7 +397,7 @@ void SquishTestTreeModel::modifyTreeItem(int row,
QModelIndex childIndex = index(row, 0, parent);
SquishTestTreeItem *toBeModified = static_cast<SquishTestTreeItem *>(itemForIndex(childIndex));
SquishTestTreeItem *toBeModified = itemForIndex(childIndex);
if (toBeModified->modifyContent(modified))
emit dataChanged(childIndex, childIndex);

View File

@@ -44,7 +44,9 @@ public:
Qt::CheckState checkState() const { return m_checked; }
bool modifyContent(const SquishTestTreeItem &other);
QString generateTestCaseName() const;
void reloadSuite();
private:
void revalidateCheckState();

View File

@@ -2,12 +2,24 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "squishtesttreeview.h"
#include "squishconstants.h"
#include "squishtesttreemodel.h"
#include "squishconstants.h"
#include "squishfilehandler.h"
#include "squishplugin.h"
#include "squishsettings.h"
#include "squishtesttreemodel.h"
#include "suiteconf.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <utils/fancylineedit.h>
#include <utils/qtcassert.h>
#include <QRegularExpression>
namespace Squish {
namespace Internal {
@@ -106,5 +118,140 @@ QSize SquishTestTreeItemDelegate::sizeHint(const QStyleOptionViewItem &option,
return QStyledItemDelegate::sizeHint(opt, idx);
}
QWidget *SquishTestTreeItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Q_UNUSED(option)
QTC_ASSERT(parent, return nullptr);
QTC_ASSERT(index.isValid(), return nullptr);
auto model = static_cast<const SquishTestTreeSortModel *>(index.model());
QTC_ASSERT(model, return nullptr);
auto srcModel = static_cast<SquishTestTreeModel *>(model->sourceModel());
const SquishTestTreeItem *suite = srcModel->itemForIndex(model->mapToSource(index.parent()));
SquishTestTreeItem *testCaseItem = srcModel->itemForIndex(model->mapToSource(index));
const SuiteConf suiteConf = SuiteConf::readSuiteConf(Utils::FilePath::fromString(suite->filePath()));
const QStringList inUse = suiteConf.usedTestCases();
Utils::FancyLineEdit *editor = new Utils::FancyLineEdit(parent);
editor->setValidationFunction([inUse](Utils::FancyLineEdit *edit, QString *) {
static const QRegularExpression validFileName("^[-a-zA-Z0-9_$. ]+$");
QString testName = edit->text();
if (!testName.startsWith("tst_"))
testName.prepend("tst_");
return validFileName.match(testName).hasMatch() && !inUse.contains(testName);
});
connect(this, &QStyledItemDelegate::closeEditor,
editor, [srcModel, testCaseItem](QWidget *, EndEditHint hint) {
QTC_ASSERT(srcModel, return);
QTC_ASSERT(testCaseItem, return);
if (hint != QAbstractItemDelegate::RevertModelCache)
return;
srcModel->destroyItem(testCaseItem);
});
return editor;
}
void SquishTestTreeItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QTC_ASSERT(editor, return);
QTC_ASSERT(index.isValid(), return);
static_cast<Utils::FancyLineEdit *>(editor)->setText(index.data().toString());
}
static Utils::FilePath scriptsPath(const Utils::FilePath &squishPath, Language language)
{
Utils::FilePath scripts = squishPath.pathAppended("scriptmodules");
switch (language) {
case Language::Python: scripts = scripts.pathAppended("python"); break;
case Language::Perl: scripts = scripts.pathAppended("perl"); break;
case Language::JavaScript: scripts = scripts.pathAppended("javascript"); break;
case Language::Ruby: scripts = scripts.pathAppended("ruby"); break;
case Language::Tcl: scripts = scripts.pathAppended("tcl"); break;
}
return scripts;
}
static bool copyScriptTemplates(const SuiteConf &suiteConf,
const Utils::FilePath &destination)
{
const SquishSettings *s = SquishPlugin::squishSettings();
QTC_ASSERT(s, return false);
// copy template files
Utils::FilePath squishPath = s->squishPath.filePath();
Utils::FilePath scripts = scriptsPath(squishPath, suiteConf.language());
bool ok = destination.ensureWritableDir();
QTC_ASSERT(ok, return false);
const bool scripted = suiteConf.objectMapStyle() == "script";
const QString extension = suiteConf.scriptExtension();
const QString testStr = scripted ? QString("script_som_template") : QString("script_template");
const Utils::FilePath test = scripts.pathAppended(testStr + extension);
const Utils::FilePath testFile = destination.pathAppended("test" + extension);
ok = test.copyFile(testFile);
QTC_ASSERT(ok, return false);
if (scripted) {
const Utils::FilePath destinationObjectMap = destination.parentDir()
.pathAppended("shared/scripts/names" + extension);
if (destinationObjectMap.exists())
return true;
const Utils::FilePath objectMap = scripts.pathAppended("objectmap_template" + extension);
ok = destinationObjectMap.parentDir().ensureWritableDir();
QTC_ASSERT(ok, return false);
ok = objectMap.copyFile(destinationObjectMap);
QTC_ASSERT(ok, return false);
}
return true;
}
void SquishTestTreeItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QTC_ASSERT(editor, return);
QTC_ASSERT(model, return);
QTC_ASSERT(index.isValid(), return);
auto sortModel = static_cast<SquishTestTreeSortModel *>(model);
auto sourceModel = static_cast<SquishTestTreeModel *>(sortModel->sourceModel());
auto lineEdit = static_cast<Utils::FancyLineEdit *>(editor);
auto removeFormerlyAdded = [sortModel, sourceModel, &index](){
auto item = sourceModel->itemForIndex(sortModel->mapToSource(index));
QTC_ASSERT(item, return);
sourceModel->destroyItem(item);
};
if (!lineEdit->isValid()) {
// remove the formerly added again
removeFormerlyAdded();
return;
}
QString chosenName = lineEdit->text();
if (!chosenName.startsWith("tst_"))
chosenName.prepend("tst_");
const QModelIndex parent = index.parent();
SquishTestTreeItem *suiteItem = sourceModel->itemForIndex(sortModel->mapToSource(parent));
const auto suiteConfPath = Utils::FilePath::fromString(suiteItem->filePath());
SuiteConf suiteConf = SuiteConf::readSuiteConf(suiteConfPath);
const Utils::FilePath destination = suiteConfPath.parentDir().pathAppended(chosenName);
bool ok = copyScriptTemplates(suiteConf, destination);
QTC_ASSERT(ok, removeFormerlyAdded(); return);
suiteConf.addTestCase(chosenName);
ok = suiteConf.write();
QTC_ASSERT(ok, removeFormerlyAdded(); return);
SquishFileHandler::instance()->openTestSuite(suiteConfPath, true);
Core::EditorManager::openEditor(destination.pathAppended("test" + suiteConf.scriptExtension()));
}
} // namespace Internal
} // namespace Squish

View File

@@ -43,6 +43,12 @@ public:
const QStyleOptionViewItem &option,
const QModelIndex &idx) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override;
};
} // namespace Internal

View File

@@ -3,6 +3,9 @@
#include "suiteconf.h"
#include <coreplugin/documentmanager.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QRegularExpression>
@@ -33,6 +36,32 @@ bool SuiteConf::read()
return true;
}
static QString languageEntry(Language language)
{
switch (language) {
case Language::Python: return "Python";
case Language::Perl: return "Perl";
case Language::JavaScript: return "JavaScript";
case Language::Ruby: return "Ruby";
case Language::Tcl: return "Tcl";
}
return {};
}
bool SuiteConf::write()
{
Core::DocumentManager::expectFileChange(m_filePath);
QSettings suiteConf(m_filePath.toString(), QSettings::IniFormat);
suiteConf.setValue(squishAutKey, m_aut);
suiteConf.setValue(squishLanguageKey, languageEntry(m_language));
suiteConf.setValue(objectsMapKey, m_objectMap);
if (!m_objectMap.isEmpty())
suiteConf.setValue(objectMapStyleKey, m_objectMapStyle);
suiteConf.setValue(squishTestCasesKey, m_testcases);
suiteConf.sync();
return suiteConf.status() == QSettings::NoError;
}
QString SuiteConf::langParameter() const
{
switch (m_language) {
@@ -64,6 +93,37 @@ QStringList SuiteConf::testCases() const
return m_testcases.split(QRegularExpression("\\s+"));
}
QStringList SuiteConf::usedTestCases() const
{
QStringList result = testCases();
auto suiteDir = m_filePath.parentDir();
const Utils::FilePaths entries = Utils::filtered(
suiteDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot),
[](const Utils::FilePath &fp) {
return fp.fileName().startsWith("tst_");
});
const QStringList testCaseNames = Utils::transform(entries, &Utils::FilePath::fileName);
for (const QString &testCaseName : testCaseNames) {
if (result.contains(testCaseName))
continue;
result.append(testCaseName); // should this check for test.*?
}
return result;
}
void SuiteConf::addTestCase(const QString &name)
{
QStringList current = testCases();
int insertAt = 0;
for (int count = current.count(); insertAt < count; ++insertAt) {
if (current.at(insertAt) > name)
break;
}
current.insert(insertAt, name);
m_testcases = current.join(' ');
}
void SuiteConf::setLanguage(const QString &language)
{
if (language == "Python")

View File

@@ -21,6 +21,7 @@ public:
static QStringList validTestCases(const QString &baseDirectory);
bool read();
bool write();
QString aut() const { return m_aut; }
void setAut(const QString &aut) { m_aut = aut; }
@@ -32,7 +33,9 @@ public:
QString objectMapStyle() const { return m_objectMapStyle; }
QString scriptExtension() const;
QStringList testCases() const;
void addTestCase(const QString &testCase);
QStringList usedTestCases() const;
private:
void setLanguage(const QString &language);