AutoTest: Allow basic filtering of scanned folders

This allows to specify folders to be used as search folders
while scanning for tests.
Current approach allows simple folder names or folder structures
without wildcards.

Examples:
Value                What will be (recursively) scanned
tests                if the current project has any (not necessarily
                     a direct) subfolder 'tests' this folder will be
                     scanned
tests/auto           if the current project has any (not necessarily
                     a direct) subfolder 'tests' and this folder has
                     a direct subfolder 'auto' the 'auto' folder will
                     be scanned
If there are more folders which apply to the rules then all of them
will be scanned.
This filtering will not keep the parser inside these folders as it
might be necessary to step into different folders because of
dependencies if the found tests, but the search for entry points to
tests will be limited to these folders.

Task-number: QTCREATORBUG-16705
Change-Id: Ib93465540cd20656d033e16205807aba6830d738
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Stenger
2017-01-06 07:20:25 +01:00
parent 34504ad797
commit 67aea18cc2
6 changed files with 316 additions and 4 deletions

View File

@@ -24,18 +24,97 @@
****************************************************************************/
#include "autotestconstants.h"
#include "testcodeparser.h"
#include "testframeworkmanager.h"
#include "testsettingspage.h"
#include "testsettings.h"
#include "testtreemodel.h"
#include <coreplugin/icore.h>
#include <utils/fancylineedit.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QDialog>
#include <QDialogButtonBox>
#include <QRegExp>
namespace Autotest {
namespace Internal {
class TestFilterDialog : public QDialog
{
public:
explicit TestFilterDialog(QWidget *parent = 0, Qt::WindowFlags f = 0);
QString filterPath() const;
void setDetailsText(const QString &details) { m_details->setText(details); }
void setDefaultFilterPath(const QString &defaultPath);
private:
static bool validate(Utils::FancyLineEdit *edit, QString * /*errormessage*/);
QLabel *m_details;
Utils::FancyLineEdit *m_lineEdit;
QString m_defaultPath;
};
TestFilterDialog::TestFilterDialog(QWidget *parent, Qt::WindowFlags f)
: QDialog(parent, f),
m_details(new QLabel),
m_lineEdit(new Utils::FancyLineEdit)
{
setModal(true);
auto layout = new QVBoxLayout(this);
layout->setSizeConstraint(QLayout::SetFixedSize);
layout->addWidget(m_details);
m_lineEdit->setValidationFunction(&validate);
layout->addWidget(m_lineEdit);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
auto okButton = buttonBox->button(QDialogButtonBox::Ok);
auto cancelButton = buttonBox->button(QDialogButtonBox::Cancel);
okButton->setEnabled(false);
layout->addWidget(buttonBox);
setLayout(layout);
connect(m_lineEdit, &Utils::FancyLineEdit::validChanged, okButton, &QPushButton::setEnabled);
connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
}
QString TestFilterDialog::filterPath() const
{
static const QRegExp repetition("//+");
QString path = m_lineEdit->isValid() ? m_lineEdit->text() : m_defaultPath;
path.replace('\\', '/'); // turn windows separators into forward slashes
path.replace(repetition, "/"); // avoid duplicated separators
if (!path.startsWith('/'))
path.prepend('/');
if (!path.endsWith('/'))
path.append('/');
if (path.length() <= 2) // after surrounding with '/' this should be > 2 if valid
return QString(); // empty string marks invalid filter
return path;
}
void TestFilterDialog::setDefaultFilterPath(const QString &defaultPath)
{
m_lineEdit->setText(defaultPath);
if (m_lineEdit->isValid())
m_defaultPath = defaultPath;
else
m_lineEdit->setText(m_defaultPath);
}
bool TestFilterDialog::validate(Utils::FancyLineEdit *edit, QString *)
{
if (!edit)
return false;
const QString &value = edit->text();
if (value.isEmpty())
return false;
// should we distinguish between Windows and UNIX?
static const QRegExp valid("(\\|/)?([^?*:;\"\'|<>\t\b]+(\\|/)?)+");
return valid.exactMatch(value);
}
/**************************************************************************************************/
TestSettingsWidget::TestSettingsWidget(QWidget *parent)
: QWidget(parent)
{
@@ -49,6 +128,16 @@ TestSettingsWidget::TestSettingsWidget(QWidget *parent)
"having at least one active test framework."));
connect(m_ui.frameworkListWidget, &QListWidget::itemChanged,
this, &TestSettingsWidget::onFrameworkItemChanged);
connect(m_ui.addFilter, &QPushButton::clicked, this, &TestSettingsWidget::onAddFilterClicked);
connect(m_ui.editFilter, &QPushButton::clicked, this, &TestSettingsWidget::onEditFilterClicked);
connect(m_ui.filterTreeWidget, &QTreeWidget::activated,
this, &TestSettingsWidget::onEditFilterClicked);
connect(m_ui.removeFilter, &QPushButton::clicked,
this, &TestSettingsWidget::onRemoveFilterClicked);
connect(m_ui.filterTreeWidget, &QTreeWidget::itemSelectionChanged, [this] () {
m_ui.editFilter->setEnabled(true);
m_ui.removeFilter->setEnabled(true);
});
}
void TestSettingsWidget::setSettings(const TestSettings &settings)
@@ -58,7 +147,9 @@ void TestSettingsWidget::setSettings(const TestSettings &settings)
m_ui.omitRunConfigWarnCB->setChecked(settings.omitRunConfigWarn);
m_ui.limitResultOutputCB->setChecked(settings.limitResultOutput);
m_ui.autoScrollCB->setChecked(settings.autoScroll);
m_ui.filterGroupBox->setChecked(settings.filterScan);
populateFrameworksListWidget(settings.frameworks);
populateFiltersWidget(settings.whiteListFilters);
}
TestSettings TestSettingsWidget::settings() const
@@ -69,7 +160,9 @@ TestSettings TestSettingsWidget::settings() const
result.omitRunConfigWarn = m_ui.omitRunConfigWarnCB->isChecked();
result.limitResultOutput = m_ui.limitResultOutputCB->isChecked();
result.autoScroll = m_ui.autoScrollCB->isChecked();
result.filterScan = m_ui.filterGroupBox->isChecked();
result.frameworks = frameworks();
result.whiteListFilters = filters();
return result;
}
@@ -87,6 +180,12 @@ void TestSettingsWidget::populateFrameworksListWidget(const QHash<Core::Id, bool
}
}
void TestSettingsWidget::populateFiltersWidget(const QStringList &filters)
{
for (const QString &filter : filters)
new QTreeWidgetItem(m_ui.filterTreeWidget, {filter} );
}
QHash<Core::Id, bool> TestSettingsWidget::frameworks() const
{
const int itemCount = m_ui.frameworkListWidget->count();
@@ -100,6 +199,16 @@ QHash<Core::Id, bool> TestSettingsWidget::frameworks() const
return frameworks;
}
QStringList TestSettingsWidget::filters() const
{
QStringList result;
if (QAbstractItemModel *model = m_ui.filterTreeWidget->model()) {
for (int row = 0, count = model->rowCount(); row < count; ++row)
result.append(model->index(row, 0).data().toString());
}
return result;
}
void TestSettingsWidget::onFrameworkItemChanged()
{
for (int row = 0, count = m_ui.frameworkListWidget->count(); row < count; ++row) {
@@ -113,6 +222,45 @@ void TestSettingsWidget::onFrameworkItemChanged()
m_ui.frameworksWarnIcon->setVisible(true);
}
void TestSettingsWidget::onAddFilterClicked()
{
TestFilterDialog dialog;
dialog.setWindowTitle(tr("Add Filter"));
dialog.setDetailsText(tr("<p>Specify a filter expression to be added to the list of filters."
"<br/>Wildcards are not supported.</p>"));
if (dialog.exec() == QDialog::Accepted) {
const QString &filter = dialog.filterPath();
if (!filter.isEmpty())
new QTreeWidgetItem(m_ui.filterTreeWidget, {filter} );
}
}
void TestSettingsWidget::onEditFilterClicked()
{
const QList<QTreeWidgetItem *> &selected = m_ui.filterTreeWidget->selectedItems();
QTC_ASSERT(selected.size() == 1, return);
const QString &oldFilter = selected.first()->data(0, Qt::DisplayRole).toString();
TestFilterDialog dialog;
dialog.setWindowTitle(tr("Edit Filter"));
dialog.setDetailsText(tr("<p>Specify a filter expression that will replace \"%1\"."
"<br/>Wildcards are not supported.</p>").arg(oldFilter));
dialog.setDefaultFilterPath(oldFilter);
if (dialog.exec() == QDialog::Accepted) {
const QString &edited = dialog.filterPath();
if (!edited.isEmpty() && edited != oldFilter)
selected.first()->setData(0, Qt::DisplayRole, edited);
}
}
void TestSettingsWidget::onRemoveFilterClicked()
{
const QList<QTreeWidgetItem *> &selected = m_ui.filterTreeWidget->selectedItems();
QTC_ASSERT(selected.size() == 1, return);
m_ui.filterTreeWidget->removeItemWidget(selected.first(), 0);
delete selected.first();
}
TestSettingsPage::TestSettingsPage(const QSharedPointer<TestSettings> &settings)
: m_settings(settings), m_widget(0)
{
@@ -142,11 +290,14 @@ void TestSettingsPage::apply()
return;
const TestSettings newSettings = m_widget->settings();
bool frameworkSyncNecessary = newSettings.frameworks != m_settings->frameworks;
bool forceReparse = newSettings.whiteListFilters.toSet() != m_settings->whiteListFilters.toSet();
*m_settings = newSettings;
m_settings->toSettings(Core::ICore::settings());
TestFrameworkManager::instance()->activateFrameworksFromSettings(m_settings);
if (frameworkSyncNecessary)
TestTreeModel::instance()->syncTestFrameworks();
else if (forceReparse)
TestTreeModel::instance()->parser()->emitUpdateTestTree();
}
} // namespace Internal