2011-02-18 14:48:16 +01:00
/**************************************************************************
* *
* * This file is part of Qt Creator
* *
2012-01-26 18:33:46 +01:00
* * Copyright ( c ) 2012 Nokia Corporation and / or its subsidiary ( - ies ) .
2011-02-18 14:48:16 +01:00
* *
2011-11-02 15:59:12 +01:00
* * Contact : Nokia Corporation ( qt - info @ nokia . com )
2011-02-18 14:48:16 +01:00
* *
* *
* * GNU Lesser General Public License Usage
* *
2011-04-13 08:42:33 +02:00
* * This file may be used under the terms of the GNU Lesser General Public
* * License version 2.1 as published by the Free Software Foundation and
* * appearing in the file LICENSE . LGPL included in the packaging of this file .
* * Please review the following information to ensure the GNU Lesser General
* * Public License version 2.1 requirements will be met :
* * http : //www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
2011-02-18 14:48:16 +01:00
* *
* * In addition , as a special exception , Nokia gives you certain additional
2011-04-13 08:42:33 +02:00
* * rights . These rights are described in the Nokia Qt LGPL Exception
2011-02-18 14:48:16 +01:00
* * version 1.1 , included in the file LGPL_EXCEPTION . txt in this package .
* *
2011-04-13 08:42:33 +02:00
* * Other Usage
* *
* * Alternatively , this file may be used in accordance with the terms and
* * conditions contained in a signed written agreement between you and Nokia .
* *
2011-02-18 14:48:16 +01:00
* * If you have questions regarding the use of this file , please contact
2011-11-02 15:59:12 +01:00
* * Nokia at qt - info @ nokia . com .
2011-02-18 14:48:16 +01:00
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-03-02 11:30:43 +01:00
# include <QtTest>
# include <pp.h>
2010-09-02 15:32:04 +10:00
//TESTED_COMPONENT=src/libs/cplusplus
2009-03-03 16:32:08 +01:00
using namespace CPlusPlus ;
2009-03-02 11:30:43 +01:00
2012-03-26 15:18:01 +02:00
# define DUMP_OUTPUT(x) {QByteArray b(x);qDebug("output: [[%s]]", b.replace("\n", "<<\n").constData());}
QByteArray loadSource ( const QString & fileName )
{
QFile inf ( fileName ) ;
if ( ! inf . open ( QIODevice : : ReadOnly | QIODevice : : Text ) ) {
qDebug ( " Cannot open \" %s \" " , fileName . toUtf8 ( ) . constData ( ) ) ;
return QByteArray ( ) ;
}
QTextStream ins ( & inf ) ;
QString source = ins . readAll ( ) ;
inf . close ( ) ;
return source . toUtf8 ( ) ;
}
void saveData ( const QByteArray & data , const QString & fileName )
{
QFile inf ( fileName ) ;
if ( ! inf . open ( QIODevice : : WriteOnly | QIODevice : : Text ) ) {
qDebug ( " Cannot open \" %s \" " , fileName . toUtf8 ( ) . constData ( ) ) ;
return ;
}
inf . write ( data ) ;
inf . close ( ) ;
}
class MockClient : public Client
{
public :
struct Block {
Block ( ) : start ( 0 ) , end ( 0 ) { }
Block ( unsigned start ) : start ( start ) , end ( 0 ) { }
unsigned start ;
unsigned end ;
} ;
public :
MockClient ( Environment * env , QByteArray * output )
: m_env ( env )
, m_output ( output )
, m_pp ( this , env )
, m_includeDepth ( 0 )
{ }
virtual ~ MockClient ( ) { }
virtual void macroAdded ( const Macro & /*macro*/ ) { }
virtual void passedMacroDefinitionCheck ( unsigned /*offset*/ , const Macro & /*macro*/ ) { }
virtual void failedMacroDefinitionCheck ( unsigned /*offset*/ , const QByteArray & /*name*/ ) { }
virtual void startExpandingMacro ( unsigned /*offset*/ ,
const Macro & /*macro*/ ,
const QByteArray & /*originalText*/ ,
const QVector < MacroArgumentReference > & /*actuals*/
= QVector < MacroArgumentReference > ( ) ) { }
virtual void stopExpandingMacro ( unsigned /*offset*/ ,
const Macro & /*macro*/ ) { }
virtual void startSkippingBlocks ( unsigned offset )
{ m_skippedBlocks . append ( Block ( offset ) ) ; }
virtual void stopSkippingBlocks ( unsigned offset )
{ m_skippedBlocks . last ( ) . end = offset ; }
virtual void sourceNeeded ( QString & includedFileName , IncludeType mode ,
unsigned /*line*/ )
{
QString resolvedFileName ;
if ( mode = = IncludeLocal )
resolvedFileName = resolveLocally ( m_env - > currentFile , includedFileName ) ;
else
resolvedFileName = resolveGlobally ( includedFileName ) ;
// qDebug("resolved [[%s]] to [[%s]] from [[%s]] (%s)\n",
// includedFileName.toUtf8().constData(),
// resolvedFileName.toUtf8().constData(),
// currentFileName.toUtf8().constData(),
// (mode == IncludeLocal) ? "locally" : "globally");
if ( resolvedFileName . isEmpty ( ) )
return ;
+ + m_includeDepth ;
// qDebug("%5d %s %s", m_includeDepth, QByteArray(m_includeDepth, '+').constData(), resolvedFileName.toUtf8().constData());
sourceNeeded ( resolvedFileName ) ;
- - m_includeDepth ;
}
QString resolveLocally ( const QString & currentFileName ,
const QString & includedFileName ) const
{
QDir dir ;
if ( currentFileName . isEmpty ( ) )
dir = QDir : : current ( ) ;
else
dir = QFileInfo ( currentFileName ) . dir ( ) ;
const QFileInfo inc ( dir , includedFileName ) ;
if ( inc . exists ( ) ) {
const QString resolved = inc . filePath ( ) ;
return resolved . toUtf8 ( ) . constData ( ) ;
} else {
// std::cerr<<"Cannot find " << inc.fileName().toUtf8().constData()<<std::endl;
return QString ( ) ;
}
}
QString resolveGlobally ( const QString & currentFileName ) const
{
foreach ( const QDir & dir , m_includePaths ) {
QFileInfo f ( dir , currentFileName ) ;
if ( f . exists ( ) )
return f . filePath ( ) ;
}
return QString ( ) ;
}
void setIncludePaths ( const QStringList & includePaths )
{
foreach ( const QString & path , includePaths ) {
QDir dir ( path ) ;
if ( dir . exists ( ) )
m_includePaths . append ( dir ) ;
}
}
void sourceNeeded ( const QString & fileName )
{
QByteArray src = loadSource ( fileName ) ;
QVERIFY ( ! src . isEmpty ( ) ) ;
m_pp . preprocess ( fileName , src , m_output , false , true , false ) ;
}
QList < Block > skippedBlocks ( ) const
{ return m_skippedBlocks ; }
private :
Environment * m_env ;
QByteArray * m_output ;
Preprocessor m_pp ;
QList < QDir > m_includePaths ;
unsigned m_includeDepth ;
QList < Block > m_skippedBlocks ;
} ;
QDebug & operator < < ( QDebug & d , const MockClient : : Block & b ) { d < < ' [ ' < < b . start < < ' , ' < < b . end < < ' ] ' ; return d ; }
2009-03-02 11:30:43 +01:00
class tst_Preprocessor : public QObject
{
2012-03-26 15:18:01 +02:00
Q_OBJECT
protected :
QByteArray preprocess ( const QString & fileName , QByteArray * /*errors*/ ) {
//### TODO: hook up errors
QByteArray output ;
Environment env ;
MockClient client ( & env , & output ) ;
client . sourceNeeded ( " data/ " + fileName ) ;
return output ;
}
2009-03-02 11:30:43 +01:00
2012-03-26 15:18:01 +02:00
private /* not corrected yet */ :
void macro_definition_lineno ( ) ;
private slots :
2012-03-16 14:06:09 +01:00
void va_args ( ) ;
void named_va_args ( ) ;
2012-03-16 14:10:46 +01:00
void first_empty_macro_arg ( ) ;
2012-03-26 15:18:01 +02:00
void invalid_param_count ( ) ;
2009-03-03 16:32:08 +01:00
void unfinished_function_like_macro_call ( ) ;
2010-02-10 11:04:31 +01:00
void nasty_macro_expansion ( ) ;
2012-02-24 13:06:21 +01:00
void tstst ( ) ;
2012-03-26 15:18:01 +02:00
void test_file_builtin ( ) ;
void blockSkipping ( ) ;
void comparisons_data ( ) ;
void comparisons ( ) ;
2009-03-02 11:30:43 +01:00
} ;
2012-03-16 14:06:09 +01:00
void tst_Preprocessor : : va_args ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
2012-03-26 15:18:01 +02:00
QByteArray ( " #define foo(...) int f(__VA_ARGS__); \n "
2012-03-16 14:06:09 +01:00
" \n foo( ) \n "
" \n foo(int a) \n "
2012-03-26 15:18:01 +02:00
" \n foo(int a,int b) \n " ) ,
true ,
false ) ;
2012-03-16 14:06:09 +01:00
2012-03-26 15:18:01 +02:00
preprocessed = preprocessed . simplified ( ) ;
// DUMP_OUTPUT(preprocessed);
2012-03-16 14:06:09 +01:00
QVERIFY ( preprocessed . contains ( " int f(); " ) ) ;
2012-03-26 15:18:01 +02:00
QVERIFY ( preprocessed . contains ( " int f( int a ); " ) ) ;
QVERIFY ( preprocessed . contains ( " int f( int a, int b ); " ) ) ;
2012-03-16 14:06:09 +01:00
}
void tst_Preprocessor : : named_va_args ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " \n #define foo(ARGS...) int f(ARGS); "
" \n foo( ) \n "
" \n foo(int a) \n "
2012-03-26 15:18:01 +02:00
" \n foo(int a,int b) \n " ) ,
true , false ) ;
2012-03-16 14:06:09 +01:00
2012-03-26 15:18:01 +02:00
preprocessed = preprocessed . simplified ( ) ;
2012-03-16 14:06:09 +01:00
QVERIFY ( preprocessed . contains ( " int f(); " ) ) ;
2012-03-26 15:18:01 +02:00
QVERIFY ( preprocessed . contains ( " int f( int a ); " ) ) ;
QVERIFY ( preprocessed . contains ( " int f( int a, int b ); " ) ) ;
2012-03-16 14:06:09 +01:00
}
2012-03-16 14:10:46 +01:00
void tst_Preprocessor : : first_empty_macro_arg ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " \n #define foo(a,b) a int b; "
" \n foo(const,cVal) \n "
" \n foo(,Val) \n "
2012-03-26 15:18:01 +02:00
" \n foo( ,Val2) \n " ) ,
true , false ) ;
2012-03-16 14:10:46 +01:00
2012-03-26 15:18:01 +02:00
preprocessed = preprocessed . simplified ( ) ;
// DUMP_OUTPUT(preprocessed);
QVERIFY ( preprocessed . contains ( " const int cVal ; " ) ) ;
QVERIFY ( preprocessed . contains ( " int Val ; " ) ) ;
QVERIFY ( preprocessed . contains ( " int Val2 ; " ) ) ;
2012-03-16 14:10:46 +01:00
}
2012-03-26 15:18:01 +02:00
void tst_Preprocessor : : invalid_param_count ( )
2012-03-16 14:22:17 +01:00
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
2012-03-26 15:18:01 +02:00
// The following is illegal, but shouldn't crash the preprocessor.
// GCC says: 3:14: error: macro "foo" requires 2 arguments, but only 1 given
2012-03-16 14:22:17 +01:00
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " \n #define foo(a,b) int f(a,b); "
" \n #define ARGS(t) t a,t b "
2012-03-26 15:18:01 +02:00
" \n foo(ARGS(int)) " ) ,
true , false ) ;
// do not verify the output: it's illegal, so anything might be outputted.
2012-03-16 14:22:17 +01:00
}
2012-03-16 15:16:06 +01:00
void tst_Preprocessor : : macro_definition_lineno ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " #define foo(ARGS) int f(ARGS) \n "
" foo(int a); \n " ) ) ;
QVERIFY ( preprocessed . contains ( " #gen true \n # 2 " ) ) ;
preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " #define foo(ARGS) int f(ARGS) \n "
" foo(int a) \n "
" ; \n " ) ) ;
QVERIFY ( preprocessed . contains ( " #gen true \n # 2 " ) ) ;
preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " #define foo(ARGS) int f(ARGS) \n "
" foo(int \n "
" a); \n " ) ) ;
QVERIFY ( preprocessed . contains ( " #gen true \n # 2 " ) ) ;
preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " #define foo int f \n "
" foo; \n " ) ) ;
QVERIFY ( preprocessed . contains ( " #gen true \n # 2 " ) ) ;
preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " #define foo int f \n "
" foo \n "
" ; \n " ) ) ;
QVERIFY ( preprocessed . contains ( " #gen true \n # 2 " ) ) ;
}
2009-03-03 16:32:08 +01:00
void tst_Preprocessor : : unfinished_function_like_macro_call ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
2009-06-19 11:19:41 +02:00
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) ,
QByteArray ( " \n #define foo(a,b) a + b "
" \n foo(1, 2 \n " ) ) ;
2012-03-26 15:18:01 +02:00
QByteArray expected__ ( " \n \n 1 \n #gen true \n # 2 \" <stdin> \" \n + \n #gen false \n # 3 \" <stdin> \" \n 2 \n " ) ;
QCOMPARE ( preprocessed , expected__ ) ;
2009-03-03 16:32:08 +01:00
}
2010-02-10 11:04:31 +01:00
void tst_Preprocessor : : nasty_macro_expansion ( )
{
QByteArray input ( " \n "
" #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) \n "
" #define is_power_of_two(x) ( !((x) & ((x)-1)) ) \n "
" #define low_bit_mask(x) ( ((x)-1) & ~(x) ) \n "
" #define is_valid_mask(x) is_power_of_two(1LU + (x) + low_bit_mask(x)) \n "
" #define compile_ffs2(__x) \\ \n "
" __builtin_choose_expr(((__x) & 0x1), 0, 1) \n "
" #define compile_ffs4(__x) \\ \n "
" __builtin_choose_expr(((__x) & 0x3), \\ \n "
" (compile_ffs2((__x))), \\ \n "
" (compile_ffs2((__x) >> 2) + 2)) \n "
" #define compile_ffs8(__x) \\ \n "
" __builtin_choose_expr(((__x) & 0xf), \\ \n "
" (compile_ffs4((__x))), \\ \n "
" (compile_ffs4((__x) >> 4) + 4)) \n "
" #define compile_ffs16(__x) \\ \n "
" __builtin_choose_expr(((__x) & 0xff), \\ \n "
" (compile_ffs8((__x))), \\ \n "
" (compile_ffs8((__x) >> 8) + 8)) \n "
" #define compile_ffs32(__x) \\ \n "
" __builtin_choose_expr(((__x) & 0xffff), \\ \n "
" (compile_ffs16((__x))), \\ \n "
" (compile_ffs16((__x) >> 16) + 16)) \n "
" #define FIELD_CHECK(__mask, __type) \\ \n "
" BUILD_BUG_ON(!(__mask) || \\ \n "
" !is_valid_mask(__mask) || \\ \n "
" (__mask) != (__type)(__mask)) \\ \n "
" \n "
" #define FIELD32(__mask) \\ \n "
" ({ \\ \n "
" FIELD_CHECK(__mask, u32); \\ \n "
" (struct rt2x00_field32) { \\ \n "
" compile_ffs32(__mask), (__mask) \\ \n "
" }; \\ \n "
" }) \n "
" #define BBPCSR 0x00f0 \n "
" #define BBPCSR_BUSY FIELD32(0x00008000) \n "
" #define WAIT_FOR_BBP(__dev, __reg) \\ \n "
" rt2x00pci_regbusy_read((__dev), BBPCSR, BBPCSR_BUSY, (__reg)) \n "
" if (WAIT_FOR_BBP(rt2x00dev, ®)) {} \n "
) ;
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess ( QLatin1String ( " <stdin> " ) , input ) ;
QVERIFY ( ! preprocessed . contains ( " FIELD32 " ) ) ;
}
2012-02-24 13:06:21 +01:00
void tst_Preprocessor : : tstst ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess (
QLatin1String ( " <stdin> " ) ,
QByteArray ( " \n "
" # define _GLIBCXX_VISIBILITY(V) __attribute__ ((__visibility__ (#V))) \n "
" namespace std _GLIBCXX_VISIBILITY(default) { \n "
" } \n "
) ) ;
2012-03-26 15:18:01 +02:00
const QByteArray result____ = " \n \n "
" namespace std \n "
2012-03-13 11:50:56 +01:00
" #gen true \n "
2012-03-26 15:18:01 +02:00
" # 2 \" <stdin> \" \n "
" __attribute__ ((__visibility__ ( \n "
" \" default \" \n "
" # 2 \" <stdin> \" \n "
" ))) \n "
2012-03-13 11:50:56 +01:00
" #gen false \n "
" # 3 \" <stdin> \" \n "
" { \n "
2012-03-26 15:18:01 +02:00
" } \n " ;
QCOMPARE ( preprocessed , result____ ) ;
}
void tst_Preprocessor : : test_file_builtin ( )
{
Client * client = 0 ; // no client.
Environment env ;
Preprocessor preprocess ( client , & env ) ;
QByteArray preprocessed = preprocess (
QLatin1String ( " some-file.c " ) ,
QByteArray ( " const char *f = __FILE__ \n "
) ) ;
const QByteArray result____ =
" const char *f = \n "
" #gen true \n "
" # 1 \" some-file.c \" \n "
" \" some-file.c \" \n "
" #gen false \n "
" # 2 \" some-file.c \" \n "
;
QCOMPARE ( preprocessed , result____ ) ;
}
void tst_Preprocessor : : comparisons_data ( )
{
QTest : : addColumn < QString > ( " infile " ) ;
QTest : : addColumn < QString > ( " outfile " ) ;
QTest : : addColumn < QString > ( " errorfile " ) ;
QTest : : newRow ( " do nothing " ) < < " noPP.1.cpp " < < " noPP.1.cpp " < < " " ;
QTest : : newRow ( " identifier-expansion 1 " ) < < " identifier-expansion.1.cpp " < < " identifier-expansion.1.out.cpp " < < " " ;
QTest : : newRow ( " identifier-expansion 2 " ) < < " identifier-expansion.2.cpp " < < " identifier-expansion.2.out.cpp " < < " " ;
QTest : : newRow ( " identifier-expansion 3 " ) < < " identifier-expansion.3.cpp " < < " identifier-expansion.3.out.cpp " < < " " ;
QTest : : newRow ( " identifier-expansion 4 " ) < < " identifier-expansion.4.cpp " < < " identifier-expansion.4.out.cpp " < < " " ;
QTest : : newRow ( " identifier-expansion 5 " ) < < " identifier-expansion.5.cpp " < < " identifier-expansion.5.out.cpp " < < " " ;
QTest : : newRow ( " reserved 1 " ) < < " reserved.1.cpp " < < " reserved.1.out.cpp " < < " " ;
QTest : : newRow ( " recursive 1 " ) < < " recursive.1.cpp " < < " recursive.1.out.cpp " < < " " ;
QTest : : newRow ( " macro_pounder_fn " ) < < " macro_pounder_fn.c " < < " " < < " " ;
QTest : : newRow ( " macro_expand " ) < < " macro_expand.c " < < " macro_expand.out.c " < < " " ;
QTest : : newRow ( " macro-test " ) < < " macro-test.cpp " < < " macro-test.out.cpp " < < " " ;
QTest : : newRow ( " empty-macro " ) < < " empty-macro.cpp " < < " empty-macro.out.cpp " < < " " ;
QTest : : newRow ( " empty-macro 2 " ) < < " empty-macro.2.cpp " < < " empty-macro.2.out.cpp " < < " " ;
}
void tst_Preprocessor : : comparisons ( )
{
QFETCH ( QString , infile ) ;
QFETCH ( QString , outfile ) ;
QFETCH ( QString , errorfile ) ;
QByteArray errors ;
QByteArray preprocessed = preprocess ( infile , & errors ) ;
// DUMP_OUTPUT(preprocessed);
if ( ! outfile . isEmpty ( ) ) {
QByteArray output____ = loadSource ( " data/ " + outfile ) ; // these weird underscores are here to make the name as long as "preprocessed", so the QCOMPARE error messages are nicely aligned.
// QCOMPARE(preprocessed, output____);
QCOMPARE ( QString : : fromUtf8 ( preprocessed . constData ( ) ) , QString : : fromUtf8 ( output____ . constData ( ) ) ) ;
}
if ( ! errorfile . isEmpty ( ) ) {
QByteArray errorFileContents = loadSource ( " data/ " + errorfile ) ;
QCOMPARE ( QString : : fromUtf8 ( errors . constData ( ) ) , QString : : fromUtf8 ( errorFileContents . constData ( ) ) ) ;
}
}
void tst_Preprocessor : : blockSkipping ( )
{
QByteArray output ;
Environment env ;
MockClient client ( & env , & output ) ;
Preprocessor pp ( & client , & env ) ;
/*QByteArray preprocessed =*/ pp (
QLatin1String ( " <stdin> " ) ,
QByteArray ( " #if 0 \n "
" \n "
" int yes; \n "
" \n "
" #elif 0 \n "
" \n "
" int no; \n "
" \n "
" #else // foobar \n "
" \n "
" void also_not; \n "
" \n "
" #endif \n "
) ) ;
2012-03-13 11:50:56 +01:00
2012-03-26 15:18:01 +02:00
QList < MockClient : : Block > blocks = client . skippedBlocks ( ) ;
QCOMPARE ( blocks . size ( ) , 1 ) ;
MockClient : : Block b = blocks . at ( 0 ) ;
QCOMPARE ( b . start , 6U ) ;
QCOMPARE ( b . end , 34U ) ;
2012-02-24 13:06:21 +01:00
}
2009-03-02 11:30:43 +01:00
QTEST_APPLESS_MAIN ( tst_Preprocessor )
# include "tst_preprocessor.moc"