Warning, file /kdevelop/kdev-python/parser/tests/pyasttest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2012 Sven Brauch svenbrauch @googlemail.com
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <language/duchain/topducontext.h>
0008 #include <language/codegen/coderepresentation.h>
0009 #include <tests/autotestshell.h>
0010 #include <tests/testcore.h>
0011 #include <language/duchain/duchain.h>
0012 #include <language/duchain/types/functiontype.h>
0013 #include <language/duchain/aliasdeclaration.h>
0014 
0015 #include "parsesession.h"
0016 #include "pythoneditorintegrator.h"
0017 #include "kdevpythonversion.h"
0018 #include "declarationbuilder.h"
0019 #include "usebuilder.h"
0020 #include "astdefaultvisitor.h"
0021 #include "expressionvisitor.h"
0022 #include "contextbuilder.h"
0023 #include "astbuilder.h"
0024 
0025 #include "duchain/helpers.h"
0026 
0027 #include "pyasttest.h"
0028 #include "../astbuilder.h"
0029 #include "../parserdebug.h"
0030 
0031 #include <ktexteditor_version.h>
0032 
0033 #include <QDebug>
0034 #include <QtTest>
0035 
0036 using namespace Python;
0037 
0038 QTEST_MAIN(PyAstTest)
0039 
0040 PyAstTest::PyAstTest(QObject* parent): QObject(parent)
0041 {
0042     initShell();
0043 }
0044 
0045 void PyAstTest::initShell()
0046 {
0047     AutoTestShell::init();
0048     TestCore* core = new TestCore();
0049     core->initialize(KDevelop::Core::NoUi);
0050     DUChain::self()->disablePersistentStorage();
0051     KDevelop::CodeRepresentation::setDiskChangesForbidden(true);
0052 }
0053 
0054 CodeAst::Ptr PyAstTest::getAst(QString code)
0055 {
0056     QSharedPointer<AstBuilder> builder(new AstBuilder);
0057     CodeAst::Ptr result = builder->parse(QUrl("<empty>"), code);
0058     qDebug() << result->dump();
0059     return result;
0060 }
0061 
0062 class VerifyVisitor : public AstDefaultVisitor {
0063 public:
0064     VerifyVisitor() : AstDefaultVisitor(), m_nodecount(0) { };
0065     void visitNode(Ast* node) override {
0066         m_nodecount += 1;
0067         QVERIFY(! node || node->astType < Ast::LastAstType);
0068         AstDefaultVisitor::visitNode(node);
0069     };
0070     void visitName(NameAst* node) override {
0071         QVERIFY(! node->identifier->value.isNull());
0072         AstDefaultVisitor::visitName(node);
0073     };
0074     void visitCode(CodeAst* node) override {
0075         AstDefaultVisitor::visitCode(node);
0076         qDebug() << "done, nodes visited:" << m_nodecount;
0077     };
0078     int m_nodecount;
0079 };
0080 
0081 void PyAstTest::testCode(QString code)
0082 {
0083     CodeAst::Ptr ast = getAst(code);
0084     VerifyVisitor v;
0085     v.visitCode(ast.data());
0086 }
0087 
0088 void PyAstTest::testExceptionHandlers()
0089 {
0090     QString code = "try: pass\n"
0091                    "except FooBar as baz: pass\n"
0092                    "except Cat as baz: pass\n"
0093                    "except Dawg as asdf: pass\n";
0094     CodeAst::Ptr ast = getAst(code);
0095     VerifyVisitor v;
0096     v.visitCode(ast.data());
0097     QCOMPARE(ast->body.size(), 1);
0098     QVERIFY(ast->body.first()->astType == Ast::TryAstType);
0099     TryAst* try_ = static_cast<TryAst*>(ast->body.first());
0100     QCOMPARE(try_->handlers.size(), 3);
0101     foreach ( ExceptionHandlerAst* handler, try_->handlers ) {
0102         QVERIFY(handler->name);
0103         QCOMPARE(handler->name->astType, Ast::IdentifierAstType);
0104     }
0105 }
0106 
0107 void PyAstTest::testStatements()
0108 {
0109     QFETCH(QString, code);
0110     testCode(code);
0111 }
0112 
0113 void PyAstTest::testStatements_data()
0114 {
0115     QTest::addColumn<QString>("code");
0116     QTest::newRow("assign_int") << "a = 3";
0117     QTest::newRow("funcdef") << "def myfun(): pass";
0118 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 5, 0)
0119     QTest::newRow("asyncfuncdef") << "async def myfun(): pass";
0120     QTest::newRow("asyncfuncdef2") << "async    def myfun(): pass";
0121     QTest::newRow("asyncfuncdef_await") << "async def myfun(): await 3";
0122 #endif
0123     QTest::newRow("funcdef_args") << "def myfun(arg1, arg2): pass";
0124     QTest::newRow("funcdef_vararg") << "def myfun(arg1, *arg): pass";
0125     QTest::newRow("funcdef_kwarg") << "def myfun(**arg): pass";
0126     QTest::newRow("classdef_inheritance") << "class myclass(parent): pass";
0127     QTest::newRow("return") << "return 3";
0128     QTest::newRow("for") << "for i in 1, 2, 3: pass";
0129     QTest::newRow("while") << "while True: pass";
0130     QTest::newRow("if") << "if True: pass";
0131     QTest::newRow("ifElse") << "if True: pass\nelse:pass";
0132     QTest::newRow("with") << "with x as y: pass";
0133     QTest::newRow("raise") << "raise Exception";
0134     QTest::newRow("tryexcept") << "try:pass\nexcept:pass";
0135     QTest::newRow("tryexceptfinally") << "try:pass\nexcept:pass\nfinally:pass";
0136     QTest::newRow("assert") << "assert false";
0137     QTest::newRow("import") << "import foobar";
0138     QTest::newRow("importfrom") << "from foobar import bazbang";
0139     QTest::newRow("global") << "global x";
0140     QTest::newRow("break") << "while True: break";
0141     QTest::newRow("continue") << "while True: continue";
0142     QTest::newRow("pass") << "pass";
0143     QTest::newRow("nonlocal") << "nonlocal x";
0144     QTest::newRow("delete") << "del x";
0145     QTest::newRow("augassign_plus") << "a += b";
0146     QTest::newRow("augassign_minus") << "a -= b";
0147     QTest::newRow("augassign_mul") << "a *= b";
0148     QTest::newRow("augassign_div") << "a /= b";
0149     QTest::newRow("augassign_or") << "a &= b";
0150     QTest::newRow("augassign_and") << "a |= b";
0151     QTest::newRow("augassign_xor") << "a ^= b";
0152 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 5, 0)
0153     QTest::newRow("augassign_matmul") << "a @= b";
0154 #endif
0155 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 6, 0)
0156     QTest::newRow("varannotation1") << "primes: List[int] = []";
0157     QTest::newRow("varannotation2") << "captain: str  # Note: no initial value!";
0158 #endif
0159 
0160 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 10, 0)
0161     QTest::newRow("match_value") << "match x:\n case 1:\n  pass";
0162     QTest::newRow("match_singleton") << "match x:\n case True:\n  pass";
0163     QTest::newRow("match_sequence") << "match x:\n case [a, b]:\n  pass";
0164     QTest::newRow("match_mapping") << "match x:\n case {'type': 'update'}:\n  pass";
0165     // TODO: Mapping rest
0166     QTest::newRow("match_class") << "class Node:\n __match_args__=('type',)\ntype=1\nmatch x:\n case Node():\n  pass";
0167     QTest::newRow("match_star") << "match x:\n case [a, b, *c]:\n  pass";
0168     QTest::newRow("match_as") << "match x:\n case a:\n  pass";
0169     QTest::newRow("match_as_empty") << "match x:\n case _:\n  pass";
0170     QTest::newRow("match_or") << "match x:\n case a|b:\n  pass";
0171     QTest::newRow("match_guard") << "match x:\n case [a, b, c] if c != 0:\n  pass";
0172 #endif
0173 
0174 }
0175 
0176 void PyAstTest::testSlices()
0177 {
0178     QFETCH(QString, code);
0179     testCode(code);
0180 }
0181 
0182 void PyAstTest::testSlices_data()
0183 {
0184     QTest::addColumn<QString>("code");
0185     QTest::newRow("slice1") << "x[1]";
0186     QTest::newRow("slice2") << "x[2:3]";
0187     QTest::newRow("slice3") << "x[::]";
0188     QTest::newRow("slice4") << "x[1:2:3]";
0189     QTest::newRow("slice_ellipsis") << "myList[1:2, ..., 0]";
0190 }
0191 
0192 void PyAstTest::testOther()
0193 {
0194     QFETCH(QString, code);
0195     testCode(code);
0196 }
0197 
0198 void PyAstTest::testOther_data()
0199 {
0200     QTest::addColumn<QString>("code");
0201     QTest::newRow("args") << "foo(bar, baz, *bang, **baa)";
0202     QTest::newRow("kws") << "foo(bar=baz, bang=3)";
0203     QTest::newRow("excpthandler") << "try:pass\nexcept foo as bar: pass";
0204     QTest::newRow("alias") << "import foo as bar";
0205 }
0206 
0207 void PyAstTest::testExpressions()
0208 {
0209     QFETCH(QString, code);
0210     testCode(code);
0211 }
0212 
0213 void PyAstTest::testExpressions_data()
0214 {
0215     QTest::addColumn<QString>("code");
0216 
0217     QTest::newRow("boolop") << "b or c";
0218     QTest::newRow("binop") << "b ^ c";
0219     QTest::newRow("unop") << "not a";
0220     QTest::newRow("lambda") << "lambda x: y";
0221     QTest::newRow("ifexpr") << "3 if 4 else 5";
0222     QTest::newRow("dict") << "{}";
0223     QTest::newRow("set") << "(3, 5)";
0224     QTest::newRow("listcomp") << "[x for x in y]";
0225     QTest::newRow("setcomp") << "(x for x in y)";
0226     QTest::newRow("dictcomp") << "{x:y for x, y in z}";
0227     QTest::newRow("comp") << "x < y";
0228     QTest::newRow("number") << "3";
0229     QTest::newRow("string") << "\"foo\"";
0230     QTest::newRow("bytes") << "b\"bytes\"";
0231     QTest::newRow("yield") << "yield x";
0232     QTest::newRow("name") << "foo";
0233     QTest::newRow("call") << "foo()";
0234     QTest::newRow("attribute") << "foo.bar";
0235     QTest::newRow("attribute_nontoplevel") << "while True: foo.bar";
0236     QTest::newRow("subscript") << "foo[3]";
0237     QTest::newRow("starred") << "*[1, 2, 3 ,4]";
0238     QTest::newRow("list") << "[]";
0239     QTest::newRow("tuple") << "()";
0240     QTest::newRow("None") << "None";
0241     QTest::newRow("False") << "False";
0242     QTest::newRow("True") << "True";
0243     QTest::newRow("Set") << "{1, 2, 3}";
0244     QTest::newRow("SetComp") << "{1 for x in [1, 2, 3]}";
0245     QTest::newRow("ExtSlice") << "A[1:3,3:5]";
0246     QTest::newRow("FString") << "f\"hi {a.b}\"";
0247     QTest::newRow("Formatted") << "'%s %s' % ('one', 'two')";
0248 
0249 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 6, 0)
0250     QTest::newRow("async_generator") << "async def foo(): result = [i async for i in aiter() if i % 2]";
0251     QTest::newRow("await_generator") << "async def foo(): result = [await fun() for fun in funcs]";
0252 
0253     QTest::newRow("underscore_literals") << "0x_FF_FF_FF_FF";
0254     QTest::newRow("formatted_string_literal") << "f\"He said his name is {name}.\"";
0255 #endif
0256 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 5, 0)
0257     QTest::newRow("dstar_unpack") << "ext_map = {\n"
0258         "       **{ext: self.obj_extension for ext in self.src_extensions},\n"
0259         "       **{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions},\n"
0260     "}";
0261     QTest::newRow("async_for") << "[i async for i in foo()]";
0262     QTest::newRow("async_for_stmt") << "async for x in y: pass";
0263     QTest::newRow("async_with_stmt") << "async with x as y: pass";
0264 #endif
0265 #if PYTHON_VERSION >= QT_VERSION_CHECK(3, 8, 0)
0266     QTest::newRow("assignment_expr_1") << "a = (b := 10)";
0267     QTest::newRow("assignment_expr_2") << "a = [q for z in (1, 2, 3) if (q := 2*z)]";
0268     QTest::newRow("positional_params") << "def foo(a, b, /, c, d, *, e): pass";
0269 #endif
0270 }
0271 
0272 void PyAstTest::testCorrectedFuncRanges()
0273 {
0274     QFETCH(QString, code);
0275     QFETCH(KTextEditor::Range, range);
0276 
0277     CodeAst::Ptr ast = getAst(code);
0278     QVERIFY(ast);
0279     foreach ( Ast* node, ast->body ) {
0280         if ( node->astType != Ast::FunctionDefinitionAstType ) {
0281             continue;
0282         }
0283         FunctionDefinitionAst* func = static_cast<FunctionDefinitionAst*>(node);
0284         QVERIFY(func->name);
0285         qCDebug(KDEV_PYTHON_PARSER) << func->name->range() << range;
0286         QCOMPARE(func->name->range(), range);
0287     }
0288 }
0289 
0290 void PyAstTest::testCorrectedFuncRanges_data()
0291 {
0292     QTest::addColumn<QString>("code");
0293     QTest::addColumn<KTextEditor::Range>("range");
0294 
0295     QTest::newRow("decorator") << "@decorate\ndef func(arg): pass" << KTextEditor::Range(1, 4, 1, 7);
0296     QTest::newRow("decorator_arg") << "@decorate(yomama=3)\ndef func(arg): pass" << KTextEditor::Range(1, 4, 1, 7);
0297     QTest::newRow("two_decorators") << "@decorate2\n@decorate\ndef func(arg): pass" << KTextEditor::Range(2, 4, 2, 7);
0298     QTest::newRow("decorate_class") << "class foo:\n @decorate2\n @decorate\n def func(arg): pass" << KTextEditor::Range(3, 5, 3, 8);
0299 }
0300 
0301 void PyAstTest::testNewPython3()
0302 {
0303     QFETCH(QString, code);
0304     testCode(code);
0305 }
0306 
0307 void PyAstTest::testNewPython3_data()
0308 {
0309     QTest::addColumn<QString>("code");
0310     QTest::newRow("funcannotation1") << "def haul(item: Haulable, *vargs: PackAnimal) -> Distance: pass";
0311     QTest::newRow("funcannotation2") << "def foo() -> expr: pass";
0312     QTest::newRow("funcannotation3") << "def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9): pass";
0313     QTest::newRow("kwonly1") << "def compare(a, b, *, key=None): pass";
0314     QTest::newRow("kwonly2") << "def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9): pass";
0315     QTest::newRow("listunpack") << "(a, *rest, b) = range(5)";
0316     QTest::newRow("metaclass") << "class C(metaclass=M): pass";
0317     QTest::newRow("exception_chain") << "raise SecondaryException() from primary_exception";
0318     QTest::newRow("yield_from") << "def foo(): yield from [1, 2, 3]";
0319 }
0320 
0321 void PyAstTest::testClass()
0322 {
0323     testCode("class c: pass");
0324 }
0325 
0326 #include "moc_pyasttest.cpp"