Skip to content

Commit

Permalink
Add test to ensure help examples are valid expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Apr 11, 2023
1 parent dc7d33a commit 2ef0734
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 92 deletions.
2 changes: 2 additions & 0 deletions python/core/auto_generated/expression/qgsexpression.sip.in
Expand Up @@ -10,6 +10,7 @@




class QgsExpression
{
%Docstring(signature="appended")
Expand Down Expand Up @@ -602,6 +603,7 @@ quotations where required.
%End



static QString helpText( QString name );
%Docstring
Returns the help text for a specified function.
Expand Down
4 changes: 2 additions & 2 deletions scripts/process_function_template.py
Expand Up @@ -78,7 +78,7 @@ def quote(v):
if not 1 <= len(a_list) <= 2:
raise BaseException("%s: 1 or 2 arguments expected for operator found %i" % (f, len(a_list)))

cpp.write("\n\n functionHelpTexts().insert( QStringLiteral( {0} ),\n Help( QStringLiteral( {0} ), tr( \"{1}\" ), tr( \"{2}\" ),\n QList<HelpVariant>()".format(
cpp.write("\n\n QgsExpression::functionHelpTexts().insert( QStringLiteral( {0} ),\n Help( QStringLiteral( {0} ), tr( \"{1}\" ), tr( \"{2}\" ),\n QList<HelpVariant>()".format(
name, json_params['type'], json_params['description'])
)

Expand Down Expand Up @@ -128,7 +128,7 @@ def quote(v):
n = os.path.basename(f)

with open(f) as content:
cpp.write("\n\n functionHelpTexts().insert( \"{0}\",\n Help( tr( \"{0}\" ), tr( \"group\" ), tr( \"{1}\" ), QList<HelpVariant>() ) );\n".format(
cpp.write("\n\n QgsExpression::functionHelpTexts().insert( \"{0}\",\n Help( tr( \"{0}\" ), tr( \"group\" ), tr( \"{1}\" ), QList<HelpVariant>() ) );\n".format(
n, content.read().replace("\\", "&#92;").replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')))

cpp.write("\n } );\n}\n")
Expand Down
15 changes: 8 additions & 7 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -32,13 +32,14 @@
// from parser
extern QgsExpressionNode *parseExpression( const QString &str, QString &parserErrorMsg, QList<QgsExpression::ParserError> &parserErrors );

Q_GLOBAL_STATIC( HelpTextHash, sFunctionHelpTexts )
Q_GLOBAL_STATIC( QgsStringMap, sVariableHelpTexts )
Q_GLOBAL_STATIC( QgsStringMap, sGroups )

HelpTextHash &functionHelpTexts()
HelpTextHash QgsExpression::sFunctionHelpTexts;

HelpTextHash &QgsExpression::functionHelpTexts()
{
return *sFunctionHelpTexts();
return sFunctionHelpTexts;
}

bool QgsExpression::checkExpression( const QString &text, const QgsExpressionContext *context, QString &errorMessage )
Expand Down Expand Up @@ -549,10 +550,10 @@ QString QgsExpression::helpText( QString name )
{
QgsExpression::initFunctionHelp();

if ( !sFunctionHelpTexts()->contains( name ) )
if ( !sFunctionHelpTexts.contains( name ) )
return tr( "function help for %1 missing" ).arg( name );

const Help &f = ( *sFunctionHelpTexts() )[ name ];
const Help &f = sFunctionHelpTexts[ name ];

name = f.mName;
if ( f.mType == tr( "group" ) )
Expand Down Expand Up @@ -686,9 +687,9 @@ QStringList QgsExpression::tags( const QString &name )

QgsExpression::initFunctionHelp();

if ( sFunctionHelpTexts()->contains( name ) )
if ( sFunctionHelpTexts.contains( name ) )
{
const Help &f = ( *sFunctionHelpTexts() )[ name ];
const Help &f = sFunctionHelpTexts[ name ];

for ( const HelpVariant &v : std::as_const( f.mVariants ) )
{
Expand Down
94 changes: 94 additions & 0 deletions src/core/expression/qgsexpression.h
Expand Up @@ -43,6 +43,92 @@ class QgsExpressionContext;
class QgsExpressionPrivate;
class QgsExpressionFunction;

#ifndef SIP_RUN
///@cond PRIVATE
struct HelpArg
{
HelpArg( const QString &arg, const QString &desc, bool descOnly = false, bool syntaxOnly = false,
bool optional = false, const QString &defaultVal = QString() )
: mArg( arg )
, mDescription( desc )
, mDescOnly( descOnly )
, mSyntaxOnly( syntaxOnly )
, mOptional( optional )
, mDefaultVal( defaultVal )
{}

QString mArg;
QString mDescription;
bool mDescOnly;
bool mSyntaxOnly;
bool mOptional;
QString mDefaultVal;
};

struct HelpExample
{
HelpExample( const QString &expression, const QString &returns, const QString &note = QString() )
: mExpression( expression )
, mReturns( returns )
, mNote( note )
{}

QString mExpression;
QString mReturns;
QString mNote;
};


struct HelpVariant
{
HelpVariant( const QString &name, const QString &description,
const QList<HelpArg> &arguments = QList<HelpArg>(),
bool variableLenArguments = false,
const QList<HelpExample> &examples = QList<HelpExample>(),
const QString &notes = QString(),
const QStringList &tags = QStringList() )
: mName( name )
, mDescription( description )
, mArguments( arguments )
, mVariableLenArguments( variableLenArguments )
, mExamples( examples )
, mNotes( notes )
, mTags( tags )
{}

QString mName;
QString mDescription;
QList<HelpArg> mArguments;
bool mVariableLenArguments;
QList<HelpExample> mExamples;
QString mNotes;
QStringList mTags;
};


struct Help
{
//! Constructor for expression help
Help() = default;

Help( const QString &name, const QString &type, const QString &description, const QList<HelpVariant> &variants )
: mName( name )
, mType( type )
, mDescription( description )
, mVariants( variants )
{}

QString mName;
QString mType;
QString mDescription;
QList<HelpVariant> mVariants;
};

typedef QHash<QString, Help> HelpTextHash;

///@endcond PRIVATE
#endif

/**
* \ingroup core
* \brief Class for parsing and evaluation of expressions (formerly called "search strings").
Expand Down Expand Up @@ -601,6 +687,12 @@ class CORE_EXPORT QgsExpression

//////

#ifndef SIP_RUN
///@cond PRIVATE
static HelpTextHash &functionHelpTexts();
///@endcond PRIVATE
#endif

/**
* Returns the help text for a specified function.
* \param name function name
Expand Down Expand Up @@ -727,6 +819,8 @@ class CORE_EXPORT QgsExpression

QgsExpressionPrivate *d = nullptr;

static HelpTextHash sFunctionHelpTexts;

//! \note not available in Python bindings
static void initFunctionHelp() SIP_SKIP;
//! \note not available in Python bindings
Expand Down
83 changes: 0 additions & 83 deletions src/core/expression/qgsexpression_p.h
Expand Up @@ -88,89 +88,6 @@ class QgsExpressionPrivate
};


struct HelpArg
{
HelpArg( const QString &arg, const QString &desc, bool descOnly = false, bool syntaxOnly = false,
bool optional = false, const QString &defaultVal = QString() )
: mArg( arg )
, mDescription( desc )
, mDescOnly( descOnly )
, mSyntaxOnly( syntaxOnly )
, mOptional( optional )
, mDefaultVal( defaultVal )
{}

QString mArg;
QString mDescription;
bool mDescOnly;
bool mSyntaxOnly;
bool mOptional;
QString mDefaultVal;
};

struct HelpExample
{
HelpExample( const QString &expression, const QString &returns, const QString &note = QString() )
: mExpression( expression )
, mReturns( returns )
, mNote( note )
{}

QString mExpression;
QString mReturns;
QString mNote;
};


struct HelpVariant
{
HelpVariant( const QString &name, const QString &description,
const QList<HelpArg> &arguments = QList<HelpArg>(),
bool variableLenArguments = false,
const QList<HelpExample> &examples = QList<HelpExample>(),
const QString &notes = QString(),
const QStringList &tags = QStringList() )
: mName( name )
, mDescription( description )
, mArguments( arguments )
, mVariableLenArguments( variableLenArguments )
, mExamples( examples )
, mNotes( notes )
, mTags( tags )
{}

QString mName;
QString mDescription;
QList<HelpArg> mArguments;
bool mVariableLenArguments;
QList<HelpExample> mExamples;
QString mNotes;
QStringList mTags;
};


struct Help
{
//! Constructor for expression help
Help() = default;

Help( const QString &name, const QString &type, const QString &description, const QList<HelpVariant> &variants )
: mName( name )
, mType( type )
, mDescription( description )
, mVariants( variants )
{}

QString mName;
QString mType;
QString mDescription;
QList<HelpVariant> mVariants;
};

typedef QHash<QString, Help> HelpTextHash;

HelpTextHash &functionHelpTexts();

///@endcond

#endif // QGSEXPRESSIONPRIVATE_H
26 changes: 26 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -17,6 +17,7 @@
#include <QString>
#include <QtConcurrentMap>
#include <QSignalSpy>
#include <QTextDocument>

#include <qgsapplication.h>
//header for class being tested
Expand Down Expand Up @@ -5761,6 +5762,31 @@ class TestQgsExpression: public QObject

}

void testHelpExamples()
{
// trigger initialization of function help
QgsExpression::helpText( QString() );
const HelpTextHash &functionHelp = QgsExpression::functionHelpTexts();
for ( auto helpIt = functionHelp.constBegin(); helpIt != functionHelp.constEnd(); ++ helpIt )
{
for ( auto variantIt = helpIt->mVariants.constBegin(); variantIt != helpIt->mVariants.constEnd(); ++variantIt )
{
for ( const HelpExample &example : std::as_const( variantIt->mExamples ) )
{
const QString htmlExpression = example.mExpression;
QTextDocument sourceDoc;
sourceDoc.setHtml( htmlExpression );
const QString plainTextExpression = sourceDoc.toPlainText();

QgsExpression exampleExpression( plainTextExpression );
QVERIFY2( !exampleExpression.hasParserError(),
QStringLiteral( "Expression: %1 for %2 has parser error" ).arg( plainTextExpression,
helpIt->mName ).toLocal8Bit() );
}
}
}
}

};

QGSTEST_MAIN( TestQgsExpression )
Expand Down

0 comments on commit 2ef0734

Please sign in to comment.