From 5f66639e069f276a20f27373a0c2e7dd9d94f390 Mon Sep 17 00:00:00 2001 From: Alan Alpert Date: Thu, 27 May 2010 15:35:17 +0200 Subject: Tweak aesthetics of QML viewer inside QtDemo Make the UX more controlled from QML, and tweak the frame to be better. Blur mechanism has been reworked, but still disabled by default. --- demos/qtdemo/MagicAnim.qml | 60 --------------- demos/qtdemo/colors.cpp | 2 +- demos/qtdemo/mainwindow.cpp | 12 +-- demos/qtdemo/menumanager.cpp | 35 ++++----- demos/qtdemo/menumanager.h | 3 +- demos/qtdemo/qmlShell.qml | 177 +++++++++++++++++++++++-------------------- demos/qtdemo/qtdemo.pro | 3 +- demos/qtdemo/qtdemo.qrc | 1 - 8 files changed, 118 insertions(+), 175 deletions(-) delete mode 100644 demos/qtdemo/MagicAnim.qml diff --git a/demos/qtdemo/MagicAnim.qml b/demos/qtdemo/MagicAnim.qml deleted file mode 100644 index 7ac3e1c..0000000 --- a/demos/qtdemo/MagicAnim.qml +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the demonstration applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** GNU Lesser General Public License Usage -** Alternatively, 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import Qt 4.7 - -//Emulates the in animation of the menu elements -SequentialAnimation{ - id: main; - property Item target - property int from: 0 - property int to: 100 - property int duration: 1000 - property string properties: "y" - PauseAnimation { duration: main.duration*0.20 } - NumberAnimation { target: main.target; properties: main.properties; from: main.from; to: main.to + 40; duration: main.duration*0.30 } - NumberAnimation { target: main.target; properties: main.properties; from: main.to + 40; to: main.to; duration: main.duration*0.10 } - NumberAnimation { target: main.target; properties: main.properties; from: main.to; to: main.to + 20; duration: main.duration*0.10 } - NumberAnimation { target: main.target; properties: main.properties; from: main.to + 20; to: main.to; duration: main.duration*0.10 } - NumberAnimation { target: main.target; properties: main.properties; from: main.to; to: main.to + 8; duration: main.duration*0.10 } - NumberAnimation { target: main.target; properties: main.properties; from: main.to + 8; to: main.to; duration: main.duration*0.10 } -} - diff --git a/demos/qtdemo/colors.cpp b/demos/qtdemo/colors.cpp index 07cf162..b352e3d 100644 --- a/demos/qtdemo/colors.cpp +++ b/demos/qtdemo/colors.cpp @@ -270,7 +270,7 @@ void Colors::parseArgs(int argc, char *argv[]) else if (s.startsWith("-h") || s.startsWith("-help")){ QMessageBox::warning(0, "Arguments", QString("Usage: qtdemo [-verbose] [-no-adapt] [-opengl] [-software] [-fullscreen] [-ticker[0|1]] ") - + "[-animations[0|1]] [-no-blending] [-no-sync] [-use-timer-update[0|1]] [-pause[0|1]] " + + "[-animations[0|1]] [-no-blending] [-use-blur] [-no-sync] [-use-timer-update[0|1]] [-pause[0|1]] " + "[-use-window-mask] [-no-rescale] " + "[-use-pixmaps] [-show-fps] [-show-br] [-8bit[0|1]] [-menu] [-use-loop] [-use-balls] " + "[-animation-speed] [-fps] " diff --git a/demos/qtdemo/mainwindow.cpp b/demos/qtdemo/mainwindow.cpp index 45ec9a6..753014a 100644 --- a/demos/qtdemo/mainwindow.cpp +++ b/demos/qtdemo/mainwindow.cpp @@ -266,7 +266,7 @@ void MainWindow::setupSceneItems() { if (Colors::showFps){ this->fpsLabel = new DemoTextItem(QString("FPS: --"), Colors::buttonFont(), Qt::white, -1, this->scene, 0, DemoTextItem::DYNAMIC_TEXT); - this->fpsLabel->setZValue(100); + this->fpsLabel->setZValue(1000); this->fpsLabel->setPos(Colors::stageStartX, 600 - QFontMetricsF(Colors::buttonFont()).height() - 5); } @@ -311,15 +311,9 @@ void MainWindow::checkAdapt() } //Note: Because we don't adapt later in the program, if blur makes FPS plummet then we won't catch it - if (!Colors::noBlur && MenuManager::instance()->mainSceneBlur && this->mainSceneRoot){ + if (!Colors::noBlur && MenuManager::instance()->declarativeEngine && this->mainSceneRoot){ Colors::noBlur = true; - this->mainSceneRoot->setGraphicsEffect(0); - MenuManager::instance()->mainSceneBlur = 0; - if(MenuManager::instance()->qmlRoot){ - MenuManager::instance()->qmlRoot->setGraphicsEffect(0); - MenuManager::instance()->declarativeEngine->rootContext()->setContextProperty("realBlur", 0); - } - MenuManager::instance()->qmlShadow = 0; + MenuManager::instance()->declarativeEngine->rootContext()->setContextProperty("useBlur", false); if (Colors::verbose) qDebug() << "- benchmark adaption: removed blur (fps < 30)"; } diff --git a/demos/qtdemo/menumanager.cpp b/demos/qtdemo/menumanager.cpp index 9eb5664..9e2ba6b 100644 --- a/demos/qtdemo/menumanager.cpp +++ b/demos/qtdemo/menumanager.cpp @@ -59,8 +59,6 @@ MenuManager::MenuManager() this->tickerInAnim = 0; this->upButton = 0; this->downButton = 0; - this->mainSceneBlur = 0; - this->qmlShadow = 0; this->helpEngine = 0; this->score = new Score(); this->currentMenu = QLatin1String("[no menu visible]"); @@ -381,8 +379,14 @@ void MenuManager::launchQmlExample(const QString &name) } } + QPainter painter(qmlBgImage); + this->window->fpsLabel->setOpacity(0); + this->window->render(&painter); + this->window->fpsLabel->setOpacity(1.0); + Qt::ImageConversionFlags convFlags = Qt::AvoidDither | Qt::NoOpaqueDetection; + this->declarativeEngine->rootContext()->setContextProperty("bgAppPixmap", QVariant(QPixmap::fromImage(*qmlBgImage, convFlags))); qmlRoot->setProperty("show", QVariant(true)); - qmlRoot->setProperty("source", file.fileName()); + qmlRoot->setProperty("qmlFile", QUrl::fromLocalFile(file.fileName())); } void MenuManager::exampleFinished() @@ -401,15 +405,6 @@ void MenuManager::init(MainWindow *window) { this->window = window; - //Create blur for later use - // Note that blur is DISABLED by default because it's too slow, even on Desktop machines - if(!Colors::noBlur){ - this->mainSceneBlur = new QGraphicsBlurEffect(this); - this->mainSceneBlur->setEnabled(false); - this->mainSceneBlur->setBlurRadius(0); - this->window->mainSceneRoot->setGraphicsEffect(mainSceneBlur); - } - // Create div: this->createTicker(); this->createUpnDownButtons(); @@ -439,8 +434,15 @@ void MenuManager::init(MainWindow *window) // Create QML Loader qmlRegisterType("Effects", 1, 0, "Blur"); + qmlRegisterType("Effects", 1, 0, "DropShadow"); declarativeEngine = new QDeclarativeEngine(this); - MenuManager::instance()->declarativeEngine->rootContext()->setContextProperty("realBlur", this->mainSceneBlur); + + // Note that we paint the background into a static image for a theorized performance improvement when blurring + // It has not yet been determined what, if any, speed up this gives (but is left in 'cause the QML expects it now) + this->qmlBgImage = new QImage(window->sceneRect().size().toSize(), QImage::Format_ARGB32); + this->qmlBgImage->fill(0); + declarativeEngine->rootContext()->setContextProperty("useBlur", !Colors::noBlur); + declarativeEngine->rootContext()->setContextProperty("bgAppPixmap", QVariant(QPixmap::fromImage(*qmlBgImage))); QDeclarativeComponent component(declarativeEngine, QUrl("qrc:qml/qmlShell.qml"), this); qmlRoot = 0; if(component.isReady()) @@ -450,14 +452,9 @@ void MenuManager::init(MainWindow *window) if(qmlRoot){ qmlRoot->setHeight(this->window->scene->sceneRect().height()); qmlRoot->setWidth(this->window->scene->sceneRect().width()); - qmlRoot->setZValue(1000);//Above other items + qmlRoot->setZValue(101);//Above other items qmlRoot->setCursor(Qt::ArrowCursor); window->scene->addItem(qmlRoot); - if(!Colors::noBlur){ - qmlShadow = new QGraphicsDropShadowEffect(this); - qmlShadow->setOffset(4); - qmlRoot->setGraphicsEffect(qmlShadow); - } //Note that QML adds key handling to the app. window->viewport()->setFocusPolicy(Qt::NoFocus);//Correct keyboard focus handling diff --git a/demos/qtdemo/menumanager.h b/demos/qtdemo/menumanager.h index 3524081..e90e02c 100644 --- a/demos/qtdemo/menumanager.h +++ b/demos/qtdemo/menumanager.h @@ -85,8 +85,7 @@ public: QDeclarativeEngine* declarativeEngine; QDeclarativeItem *qmlRoot; - QGraphicsBlurEffect *mainSceneBlur; - QGraphicsDropShadowEffect *qmlShadow; + QImage *qmlBgImage; private slots: void exampleFinished(); diff --git a/demos/qtdemo/qmlShell.qml b/demos/qtdemo/qmlShell.qml index eb155c4..b9021e8 100644 --- a/demos/qtdemo/qmlShell.qml +++ b/demos/qtdemo/qmlShell.qml @@ -42,117 +42,132 @@ import Qt 4.7 import Effects 1.0 +/* Vars exposed from C++ + pixmap bgAppPixmap + bool useBlur (to turn on, pass -use-blur on the cmd line. Off by default 'cause it's too slow) +*/ Item { id: main - property alias source: loader.source + //height and width set by program to fill window + //below properties are sometimes set from C++ + property url qmlFile: '' property bool show: false - x: 0 - y: -500 //height and width set by program - opacity: 0 - property QtObject blurEffect: realBlur == null ? dummyBlur : realBlur //Is there a better way to lose those error messages? - Loader{//Automatic FocusScope - focus: true - clip: true - id: loader //source set by program - anchors.centerIn: parent - onStatusChanged: if(status == Loader.Ready) { + Image{ + id: bg + opacity: 0 + anchors.fill: parent + z: -1 + pixmap: bgAppPixmap + effect: Blur { id: blurEffect; enabled: useBlur; blurRadius: 8;} + } + + Item{ id:embeddedViewer + width: parent.width + height: parent.height + opacity: 0; + z: 10 + Loader{ + id: loader + z: 10 + focus: true //Automatic FocusScope + clip: true + source: qmlFile + anchors.centerIn: parent + onStatusChanged: if(status == Loader.Ready) { if(loader.item.width > 640) loader.item.width = 640; if(loader.item.height > 480) loader.item.height = 480; - } + } - } - Rectangle{ - z: -1 - anchors.fill: loader.status == Loader.Ready ? loader : errorTxt - anchors.margins: -10 - radius: 12 - smooth: true - gradient: Gradient{ - GradientStop{ position: 0.0; color: "#14FFFFFF" } - GradientStop{ position: 1.0; color: "#5AFFFFFF" } } - MouseArea{ - anchors.fill: parent - onClicked: loader.focus=true;/* and don't propogate to the 'exit' area*/ + Rectangle{ id: frame + z: 9 + anchors.fill: loader.status == Loader.Ready ? loader : errorTxt + anchors.margins: -8 + radius: 4 + smooth: true + border.color: "#88aaaaaa" + gradient: Gradient{ + GradientStop{ position: 0.0; color: "#14FFFFFF" } + GradientStop{ position: 1.0; color: "#5AFFFFFF" } + } + MouseArea{ + anchors.fill: parent + onClicked: loader.focus=true;/* and don't propogate to the 'exit' area*/ + } + + Rectangle{ id: innerFrame + anchors.margins: 7 + anchors.bottomMargin: 8 + anchors.rightMargin: 8 + color: "black" + border.color: "#44000000" + anchors.fill:parent + } + + effect: DropShadow { + enabled: useBlur; + blurRadius: 9; + color: "#88000000"; + xOffset:0 + yOffset:0 + } } + Text{ + id: errorTxt + z: 10 + anchors.centerIn: parent + color: 'white' + smooth: true + visible: loader.status == Loader.Error + textFormat: Text.RichText + //Note that if loader is Error, it is because the file was found but there was an error creating the component + //This means either we have a bug in our demos, or the required modules (which ship with Qt) did not deploy correctly + text: "The example has failed to load.
If you installed Qt's QML modules this is a bug!
" + + 'Report it at http://bugreports.qt.nokia.com'; + onLinkActivated: Qt.openUrlExternally(link); + } + } + Rectangle{ id: blackout //Maybe use a colorize effect instead? + z: 8 + anchors.fill: parent + color: "#000000" + opacity: 0 } - MouseArea{ - z: -2 - hoverEnabled: true //To steal from the buttons + z: 8 + enabled: main.show + hoverEnabled: true //To steal focus from the buttons anchors.fill: parent onClicked: main.show=false; } - Text{ - id: errorTxt - anchors.centerIn: parent - color: 'white' - smooth: true - visible: loader.status == Loader.Error - textFormat: Text.RichText //includes link for bugreport - //Note that if loader is Error, it is because the file was found but there was an error creating the component - //This means either we have a bug in our demos, or the required modules (which ship with Qt) did not deploy correctly - text: 'The example has failed to load. This is a bug!
' - +'Report it at http://bugreports.qt.nokia.com'; - onLinkActivated: Qt.openUrlExternally(link); - } - - states: [ State { name: "show" when: show == true PropertyChanges { - target: main + target: embeddedViewer + opacity: 1 + } + PropertyChanges { + target: bg opacity: 1 - y: 0 } PropertyChanges { - target: blurEffect - enabled: true - blurRadius: 8 - blurHints: Blur.AnimationHint | Blur.PerformanceHint + target: blackout + opacity: 0.5 } } ] - MagicAnim{ id: magicAnim; target: main; from: -500; to: 0 } - transitions: [ - Transition { from: ""; to: "show" - SequentialAnimation{ - ScriptAction{ script: magicAnim.start() } - NumberAnimation{ properties: "opacity,blurRadius"; easing.type: Easing.OutCubic; duration: 1000} - PropertyAnimation{ target: main; property: "y"} - } - - }, - Transition { from: "show"; to: "" //Addtionally, unload the item + transitions: [//Should not be too long, because the component has already started running + Transition { from: ''; to: "show"; reversible: true SequentialAnimation{ - NumberAnimation{ properties: "y,opacity,blurRadius";duration: 500 } - ScriptAction{ script: loader.source = ''; } + PropertyAction { target: bg; property: useBlur?"y":"opacity";}//fade in blurred background only if blurred + NumberAnimation{ properties: "opacity"; easing.type: Easing.InQuad; duration: 500} } - /*Attempt to copy the exeunt animation. Looks bad - SequentialAnimation{ - ParallelAnimation{ - NumberAnimation{ properties: "opacity,blurRadius"; easing.type: Easing.InCubic; duration: 1000 } - SequentialAnimation{ - NumberAnimation{ target: main; property: 'y'; to: 3.2*height/5; duration: 500} - ParallelAnimation{ - NumberAnimation{ target: main; property: 'y'; to: 3.0*height/5; duration: 100} - NumberAnimation{ target: main; property: 'x'; to: 3.0*width/5; duration: 100} - } - NumberAnimation{ target: main; property: 'x'; to: 700; duration: 400} - } - } - ScriptAction{ script: loader.source = ''; } - PropertyAction{ properties: "x,y";} - } - */ } ] - Item{ Blur{id: dummyBlur } } - } diff --git a/demos/qtdemo/qtdemo.pro b/demos/qtdemo/qtdemo.pro index 5e64e1c..4d4177e 100644 --- a/demos/qtdemo/qtdemo.pro +++ b/demos/qtdemo/qtdemo.pro @@ -75,5 +75,4 @@ sources.files = $$SOURCES $$HEADERS $$FORMS $$RESOURCES qtdemo.pro images xml *. sources.path = $$[QT_INSTALL_DEMOS]/qtdemo OTHER_FILES += \ - qmlShell.qml \ - MagicAnim.qml + qmlShell.qml diff --git a/demos/qtdemo/qtdemo.qrc b/demos/qtdemo/qtdemo.qrc index 7682ab5..c18420f 100644 --- a/demos/qtdemo/qtdemo.qrc +++ b/demos/qtdemo/qtdemo.qrc @@ -7,6 +7,5 @@ qmlShell.qml - MagicAnim.qml -- cgit v0.12 n471'>471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
import sys
sys.path = ['.'] + sys.path

from test.test_support import verbose, run_unittest
import re
from sre import Scanner
import sys, os, traceback
from weakref import proxy

# Misc tests from Tim Peters' re.doc

# WARNING: Don't change details in these tests if you don't know
# what you're doing. Some of these tests were carefuly modeled to
# cover most of the code.

import unittest

class ReTests(unittest.TestCase):

    def test_weakref(self):
        s = 'QabbbcR'
        x = re.compile('ab+c')
        y = proxy(x)
        self.assertEqual(x.findall('QabbbcR'), y.findall('QabbbcR'))

    def test_search_star_plus(self):
        self.assertEqual(re.search('x*', 'axx').span(0), (0, 0))
        self.assertEqual(re.search('x*', 'axx').span(), (0, 0))
        self.assertEqual(re.search('x+', 'axx').span(0), (1, 3))
        self.assertEqual(re.search('x+', 'axx').span(), (1, 3))
        self.assertEqual(re.search('x', 'aaa'), None)
        self.assertEqual(re.match('a*', 'xxx').span(0), (0, 0))
        self.assertEqual(re.match('a*', 'xxx').span(), (0, 0))
        self.assertEqual(re.match('x*', 'xxxa').span(0), (0, 3))
        self.assertEqual(re.match('x*', 'xxxa').span(), (0, 3))
        self.assertEqual(re.match('a+', 'xxx'), None)

    def bump_num(self, matchobj):
        int_value = int(matchobj.group(0))
        return str(int_value + 1)

    def test_basic_re_sub(self):
        self.assertEqual(re.sub("(?i)b+", "x", "bbbb BBBB"), 'x x')
        self.assertEqual(re.sub(r'\d+', self.bump_num, '08.2 -2 23x99y'),
                         '9.3 -3 24x100y')
        self.assertEqual(re.sub(r'\d+', self.bump_num, '08.2 -2 23x99y', 3),
                         '9.3 -3 23x99y')

        self.assertEqual(re.sub('.', lambda m: r"\n", 'x'), '\\n')
        self.assertEqual(re.sub('.', r"\n", 'x'), '\n')

        s = r"\1\1"
        self.assertEqual(re.sub('(.)', s, 'x'), 'xx')
        self.assertEqual(re.sub('(.)', re.escape(s), 'x'), s)
        self.assertEqual(re.sub('(.)', lambda m: s, 'x'), s)

        self.assertEqual(re.sub('(?P<a>x)', '\g<a>\g<a>', 'xx'), 'xxxx')
        self.assertEqual(re.sub('(?P<a>x)', '\g<a>\g<1>', 'xx'), 'xxxx')
        self.assertEqual(re.sub('(?P<unk>x)', '\g<unk>\g<unk>', 'xx'), 'xxxx')
        self.assertEqual(re.sub('(?P<unk>x)', '\g<1>\g<1>', 'xx'), 'xxxx')

        self.assertEqual(re.sub('a',r'\t\n\v\r\f\a\b\B\Z\a\A\w\W\s\S\d\D','a'),
                         '\t\n\v\r\f\a\b\\B\\Z\a\\A\\w\\W\\s\\S\\d\\D')
        self.assertEqual(re.sub('a', '\t\n\v\r\f\a', 'a'), '\t\n\v\r\f\a')
        self.assertEqual(re.sub('a', '\t\n\v\r\f\a', 'a'),
                         (chr(9)+chr(10)+chr(11)+chr(13)+chr(12)+chr(7)))

        self.assertEqual(re.sub('^\s*', 'X', 'test'), 'Xtest')

    def test_bug_449964(self):
        # fails for group followed by other escape
        self.assertEqual(re.sub(r'(?P<unk>x)', '\g<1>\g<1>\\b', 'xx'),
                         'xx\bxx\b')

    def test_bug_449000(self):
        # Test for sub() on escaped characters
        self.assertEqual(re.sub(r'\r\n', r'\n', 'abc\r\ndef\r\n'),
                         'abc\ndef\n')
        self.assertEqual(re.sub('\r\n', r'\n', 'abc\r\ndef\r\n'),
                         'abc\ndef\n')
        self.assertEqual(re.sub(r'\r\n', '\n', 'abc\r\ndef\r\n'),
                         'abc\ndef\n')
        self.assertEqual(re.sub('\r\n', '\n', 'abc\r\ndef\r\n'),
                         'abc\ndef\n')

    def test_sub_template_numeric_escape(self):
        # bug 776311 and friends
        self.assertEqual(re.sub('x', r'\0', 'x'), '\0')
        self.assertEqual(re.sub('x', r'\000', 'x'), '\000')
        self.assertEqual(re.sub('x', r'\001', 'x'), '\001')
        self.assertEqual(re.sub('x', r'\008', 'x'), '\0' + '8')
        self.assertEqual(re.sub('x', r'\009', 'x'), '\0' + '9')
        self.assertEqual(re.sub('x', r'\111', 'x'), '\111')
        self.assertEqual(re.sub('x', r'\117', 'x'), '\117')

        self.assertEqual(re.sub('x', r'\1111', 'x'), '\1111')
        self.assertEqual(re.sub('x', r'\1111', 'x'), '\111' + '1')

        self.assertEqual(re.sub('x', r'\00', 'x'), '\x00')
        self.assertEqual(re.sub('x', r'\07', 'x'), '\x07')
        self.assertEqual(re.sub('x', r'\08', 'x'), '\0' + '8')
        self.assertEqual(re.sub('x', r'\09', 'x'), '\0' + '9')
        self.assertEqual(re.sub('x', r'\0a', 'x'), '\0' + 'a')

        self.assertEqual(re.sub('x', r'\400', 'x'), '\0')
        self.assertEqual(re.sub('x', r'\777', 'x'), '\377')

        self.assertRaises(re.error, re.sub, 'x', r'\1', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\8', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\9', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\11', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\18', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\1a', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\90', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\99', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\118', 'x') # r'\11' + '8'
        self.assertRaises(re.error, re.sub, 'x', r'\11a', 'x')
        self.assertRaises(re.error, re.sub, 'x', r'\181', 'x') # r'\18' + '1'
        self.assertRaises(re.error, re.sub, 'x', r'\800', 'x') # r'\80' + '0'

        # in python2.3 (etc), these loop endlessly in sre_parser.py
        self.assertEqual(re.sub('(((((((((((x)))))))))))', r'\11', 'x'), 'x')
        self.assertEqual(re.sub('((((((((((y))))))))))(.)', r'\118', 'xyz'),
                         'xz8')
        self.assertEqual(re.sub('((((((((((y))))))))))(.)', r'\11a', 'xyz'),
                         'xza')

    def test_qualified_re_sub(self):
        self.assertEqual(re.sub('a', 'b', 'aaaaa'), 'bbbbb')
        self.assertEqual(re.sub('a', 'b', 'aaaaa', 1), 'baaaa')

    def test_bug_114660(self):
        self.assertEqual(re.sub(r'(\S)\s+(\S)', r'\1 \2', 'hello  there'),
                         'hello there')

    def test_bug_462270(self):
        # Test for empty sub() behaviour, see SF bug #462270
        self.assertEqual(re.sub('x*', '-', 'abxd'), '-a-b-d-')
        self.assertEqual(re.sub('x+', '-', 'abxd'), 'ab-d')

    def test_symbolic_refs(self):
        self.assertRaises(re.error, re.sub, '(?P<a>x)', '\g<a', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)', '\g<', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)', '\g', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)', '\g<a a>', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)', '\g<1a1>', 'xx')
        self.assertRaises(IndexError, re.sub, '(?P<a>x)', '\g<ab>', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)|(?P<b>y)', '\g<b>', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)|(?P<b>y)', '\\2', 'xx')
        self.assertRaises(re.error, re.sub, '(?P<a>x)', '\g<-1>', 'xx')

    def test_re_subn(self):
        self.assertEqual(re.subn("(?i)b+", "x", "bbbb BBBB"), ('x x', 2))
        self.assertEqual(re.subn("b+", "x", "bbbb BBBB"), ('x BBBB', 1))
        self.assertEqual(re.subn("b+", "x", "xyz"), ('xyz', 0))
        self.assertEqual(re.subn("b*", "x", "xyz"), ('xxxyxzx', 4))
        self.assertEqual(re.subn("b*", "x", "xyz", 2), ('xxxyz', 2))

    def test_re_split(self):
        self.assertEqual(re.split(":", ":a:b::c"), ['', 'a', 'b', '', 'c'])
        self.assertEqual(re.split(":*", ":a:b::c"), ['', 'a', 'b', 'c'])
        self.assertEqual(re.split("(:*)", ":a:b::c"),
                         ['', ':', 'a', ':', 'b', '::', 'c'])
        self.assertEqual(re.split("(?::*)", ":a:b::c"), ['', 'a', 'b', 'c'])
        self.assertEqual(re.split("(:)*", ":a:b::c"),
                         ['', ':', 'a', ':', 'b', ':', 'c'])
        self.assertEqual(re.split("([b:]+)", ":a:b::c"),
                         ['', ':', 'a', ':b::', 'c'])
        self.assertEqual(re.split("(b)|(:+)", ":a:b::c"),
                         ['', None, ':', 'a', None, ':', '', 'b', None, '',
                          None, '::', 'c'])
        self.assertEqual(re.split("(?:b)|(?::+)", ":a:b::c"),
                         ['', 'a', '', '', 'c'])

    def test_qualified_re_split(self):
        self.assertEqual(re.split(":", ":a:b::c", 2), ['', 'a', 'b::c'])
        self.assertEqual(re.split(':', 'a:b:c:d', 2), ['a', 'b', 'c:d'])
        self.assertEqual(re.split("(:)", ":a:b::c", 2),
                         ['', ':', 'a', ':', 'b::c'])
        self.assertEqual(re.split("(:*)", ":a:b::c", 2),
                         ['', ':', 'a', ':', 'b::c'])

    def test_re_findall(self):
        self.assertEqual(re.findall(":+", "abc"), [])
        self.assertEqual(re.findall(":+", "a:b::c:::d"), [":", "::", ":::"])
        self.assertEqual(re.findall("(:+)", "a:b::c:::d"), [":", "::", ":::"])
        self.assertEqual(re.findall("(:)(:*)", "a:b::c:::d"), [(":", ""),
                                                               (":", ":"),
                                                               (":", "::")])

    def test_bug_117612(self):
        self.assertEqual(re.findall(r"(a|(b))", "aba"),
                         [("a", ""),("b", "b"),("a", "")])

    def test_re_match(self):
        self.assertEqual(re.match('a', 'a').groups(), ())
        self.assertEqual(re.match('(a)', 'a').groups(), ('a',))
        self.assertEqual(re.match(r'(a)', 'a').group(0), 'a')
        self.assertEqual(re.match(r'(a)', 'a').group(1), 'a')
        self.assertEqual(re.match(r'(a)', 'a').group(1, 1), ('a', 'a'))

        pat = re.compile('((a)|(b))(c)?')
        self.assertEqual(pat.match('a').groups(), ('a', 'a', None, None))
        self.assertEqual(pat.match('b').groups(), ('b', None, 'b', None))
        self.assertEqual(pat.match('ac').groups(), ('a', 'a', None, 'c'))
        self.assertEqual(pat.match('bc').groups(), ('b', None, 'b', 'c'))
        self.assertEqual(pat.match('bc').groups(""), ('b', "", 'b', 'c'))

        # A single group
        m = re.match('(a)', 'a')
        self.assertEqual(m.group(0), 'a')
        self.assertEqual(m.group(0), 'a')
        self.assertEqual(m.group(1), 'a')
        self.assertEqual(m.group(1, 1), ('a', 'a'))

        pat = re.compile('(?:(?P<a1>a)|(?P<b2>b))(?P<c3>c)?')
        self.assertEqual(pat.match('a').group(1, 2, 3), ('a', None, None))
        self.assertEqual(pat.match('b').group('a1', 'b2', 'c3'),
                         (None, 'b', None))
        self.assertEqual(pat.match('ac').group(1, 'b2', 3), ('a', None, 'c'))

    def test_re_groupref_exists(self):
        self.assertEqual(re.match('^(\()?([^()]+)(?(1)\))$', '(a)').groups(),
                         ('(', 'a'))
        self.assertEqual(re.match('^(\()?([^()]+)(?(1)\))$', 'a').groups(),
                         (None, 'a'))
        self.assertEqual(re.match('^(\()?([^()]+)(?(1)\))$', 'a)'), None)
        self.assertEqual(re.match('^(\()?([^()]+)(?(1)\))$', '(a'), None)
        self.assertEqual(re.match('^(?:(a)|c)((?(1)b|d))$', 'ab').groups(),
                         ('a', 'b'))
        self.assertEqual(re.match('^(?:(a)|c)((?(1)b|d))$', 'cd').groups(),
                         (None, 'd'))
        self.assertEqual(re.match('^(?:(a)|c)((?(1)|d))$', 'cd').groups(),
                         (None, 'd'))
        self.assertEqual(re.match('^(?:(a)|c)((?(1)|d))$', 'a').groups(),
                         ('a', ''))

        # Tests for bug #1177831: exercise groups other than the first group
        p = re.compile('(?P<g1>a)(?P<g2>b)?((?(g2)c|d))')
        self.assertEqual(p.match('abc').groups(),
                         ('a', 'b', 'c'))
        self.assertEqual(p.match('ad').groups(),
                         ('a', None, 'd'))
        self.assertEqual(p.match('abd'), None)
        self.assertEqual(p.match('ac'), None)


    def test_re_groupref(self):
        self.assertEqual(re.match(r'^(\|)?([^()]+)\1$', '|a|').groups(),
                         ('|', 'a'))
        self.assertEqual(re.match(r'^(\|)?([^()]+)\1?$', 'a').groups(),
                         (None, 'a'))
        self.assertEqual(re.match(r'^(\|)?([^()]+)\1$', 'a|'), None)
        self.assertEqual(re.match(r'^(\|)?([^()]+)\1$', '|a'), None)
        self.assertEqual(re.match(r'^(?:(a)|c)(\1)$', 'aa').groups(),
                         ('a', 'a'))
        self.assertEqual(re.match(r'^(?:(a)|c)(\1)?$', 'c').groups(),
                         (None, None))

    def test_groupdict(self):
        self.assertEqual(re.match('(?P<first>first) (?P<second>second)',
                                  'first second').groupdict(),
                         {'first':'first', 'second':'second'})

    def test_expand(self):
        self.assertEqual(re.match("(?P<first>first) (?P<second>second)",
                                  "first second")
                                  .expand(r"\2 \1 \g<second> \g<first>"),
                         "second first second first")

    def test_repeat_minmax(self):
        self.assertEqual(re.match("^(\w){1}$", "abc"), None)
        self.assertEqual(re.match("^(\w){1}?$", "abc"), None)
        self.assertEqual(re.match("^(\w){1,2}$", "abc"), None)
        self.assertEqual(re.match("^(\w){1,2}?$", "abc"), None)

        self.assertEqual(re.match("^(\w){3}$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){1,3}$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){1,4}$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){3,4}?$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){3}?$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){1,3}?$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){1,4}?$", "abc").group(1), "c")
        self.assertEqual(re.match("^(\w){3,4}?$", "abc").group(1), "c")

        self.assertEqual(re.match("^x{1}$", "xxx"), None)
        self.assertEqual(re.match("^x{1}?$", "xxx"), None)
        self.assertEqual(re.match("^x{1,2}$", "xxx"), None)
        self.assertEqual(re.match("^x{1,2}?$", "xxx"), None)

        self.assertNotEqual(re.match("^x{3}$", "xxx"), None)
        self.assertNotEqual(re.match("^x{1,3}$", "xxx"), None)
        self.assertNotEqual(re.match("^x{1,4}$", "xxx"), None)
        self.assertNotEqual(re.match("^x{3,4}?$", "xxx"), None)
        self.assertNotEqual(re.match("^x{3}?$", "xxx"), None)
        self.assertNotEqual(re.match("^x{1,3}?$", "xxx"), None)
        self.assertNotEqual(re.match("^x{1,4}?$", "xxx"), None)
        self.assertNotEqual(re.match("^x{3,4}?$", "xxx"), None)

        self.assertEqual(re.match("^x{}$", "xxx"), None)
        self.assertNotEqual(re.match("^x{}$", "x{}"), None)

    def test_getattr(self):
        self.assertEqual(re.match("(a)", "a").pos, 0)
        self.assertEqual(re.match("(a)", "a").endpos, 1)
        self.assertEqual(re.match("(a)", "a").string, "a")
        self.assertEqual(re.match("(a)", "a").regs, ((0, 1), (0, 1)))
        self.assertNotEqual(re.match("(a)", "a").re, None)

    def test_special_escapes(self):
        self.assertEqual(re.search(r"\b(b.)\b",
                                   "abcd abc bcd bx").group(1), "bx")
        self.assertEqual(re.search(r"\B(b.)\B",
                                   "abc bcd bc abxd").group(1), "bx")
        self.assertEqual(re.search(r"\b(b.)\b",
                                   "abcd abc bcd bx", re.LOCALE).group(1), "bx")
        self.assertEqual(re.search(r"\B(b.)\B",
                                   "abc bcd bc abxd", re.LOCALE).group(1), "bx")
        self.assertEqual(re.search(r"\b(b.)\b",
                                   "abcd abc bcd bx", re.UNICODE).group(1), "bx")
        self.assertEqual(re.search(r"\B(b.)\B",
                                   "abc bcd bc abxd", re.UNICODE).group(1), "bx")
        self.assertEqual(re.search(r"^abc$", "\nabc\n", re.M).group(0), "abc")
        self.assertEqual(re.search(r"^\Aabc\Z$", "abc", re.M).group(0), "abc")
        self.assertEqual(re.search(r"^\Aabc\Z$", "\nabc\n", re.M), None)
        self.assertEqual(re.search(r"\b(b.)\b",
                                   u"abcd abc bcd bx").group(1), "bx")
        self.assertEqual(re.search(r"\B(b.)\B",
                                   u"abc bcd bc abxd").group(1), "bx")
        self.assertEqual(re.search(r"^abc$", u"\nabc\n", re.M).group(0), "abc")
        self.assertEqual(re.search(r"^\Aabc\Z$", u"abc", re.M).group(0), "abc")
        self.assertEqual(re.search(r"^\Aabc\Z$", u"\nabc\n", re.M), None)
        self.assertEqual(re.search(r"\d\D\w\W\s\S",
                                   "1aa! a").group(0), "1aa! a")
        self.assertEqual(re.search(r"\d\D\w\W\s\S",
                                   "1aa! a", re.LOCALE).group(0), "1aa! a")
        self.assertEqual(re.search(r"\d\D\w\W\s\S",
                                   "1aa! a", re.UNICODE).group(0), "1aa! a")

    def test_ignore_case(self):
        self.assertEqual(re.match("abc", "ABC", re.I).group(0), "ABC")
        self.assertEqual(re.match("abc", u"ABC", re.I).group(0), "ABC")

    def test_bigcharset(self):
        self.assertEqual(re.match(u"([\u2222\u2223])",
                                  u"\u2222").group(1), u"\u2222")
        self.assertEqual(re.match(u"([\u2222\u2223])",
                                  u"\u2222", re.UNICODE).group(1), u"\u2222")

    def test_anyall(self):
        self.assertEqual(re.match("a.b", "a\nb", re.DOTALL).group(0),
                         "a\nb")
        self.assertEqual(re.match("a.*b", "a\n\nb", re.DOTALL).group(0),
                         "a\n\nb")

    def test_non_consuming(self):
        self.assertEqual(re.match("(a(?=\s[^a]))", "a b").group(1), "a")
        self.assertEqual(re.match("(a(?=\s[^a]*))", "a b").group(1), "a")
        self.assertEqual(re.match("(a(?=\s[abc]))", "a b").group(1), "a")
        self.assertEqual(re.match("(a(?=\s[abc]*))", "a bc").group(1), "a")
        self.assertEqual(re.match(r"(a)(?=\s\1)", "a a").group(1), "a")
        self.assertEqual(re.match(r"(a)(?=\s\1*)", "a aa").group(1), "a")
        self.assertEqual(re.match(r"(a)(?=\s(abc|a))", "a a").group(1), "a")

        self.assertEqual(re.match(r"(a(?!\s[^a]))", "a a").group(1), "a")
        self.assertEqual(re.match(r"(a(?!\s[abc]))", "a d").group(1), "a")
        self.assertEqual(re.match(r"(a)(?!\s\1)", "a b").group(1), "a")
        self.assertEqual(re.match(r"(a)(?!\s(abc|a))", "a b").group(1), "a")

    def test_ignore_case(self):
        self.assertEqual(re.match(r"(a\s[^a])", "a b", re.I).group(1), "a b")
        self.assertEqual(re.match(r"(a\s[^a]*)", "a bb", re.I).group(1), "a bb")
        self.assertEqual(re.match(r"(a\s[abc])", "a b", re.I).group(1), "a b")
        self.assertEqual(re.match(r"(a\s[abc]*)", "a bb", re.I).group(1), "a bb")
        self.assertEqual(re.match(r"((a)\s\2)", "a a", re.I).group(1), "a a")
        self.assertEqual(re.match(r"((a)\s\2*)", "a aa", re.I).group(1), "a aa")
        self.assertEqual(re.match(r"((a)\s(abc|a))", "a a", re.I).group(1), "a a")
        self.assertEqual(re.match(r"((a)\s(abc|a)*)", "a aa", re.I).group(1), "a aa")

    def test_category(self):
        self.assertEqual(re.match(r"(\s)", " ").group(1), " ")

    def test_getlower(self):
        import _sre
        self.assertEqual(_sre.getlower(ord('A'), 0), ord('a'))
        self.assertEqual(_sre.getlower(ord('A'), re.LOCALE), ord('a'))
        self.assertEqual(_sre.getlower(ord('A'), re.UNICODE), ord('a'))

        self.assertEqual(re.match("abc", "ABC", re.I).group(0), "ABC")
        self.assertEqual(re.match("abc", u"ABC", re.I).group(0), "ABC")

    def test_not_literal(self):
        self.assertEqual(re.search("\s([^a])", " b").group(1), "b")
        self.assertEqual(re.search("\s([^a]*)", " bb").group(1), "bb")

    def test_search_coverage(self):
        self.assertEqual(re.search("\s(b)", " b").group(1), "b")
        self.assertEqual(re.search("a\s", "a ").group(0), "a ")

    def test_re_escape(self):
        p=""
        for i in range(0, 256):
            p = p + chr(i)
            self.assertEqual(re.match(re.escape(chr(i)), chr(i)) is not None,
                             True)
            self.assertEqual(re.match(re.escape(chr(i)), chr(i)).span(), (0,1))

        pat=re.compile(re.escape(p))
        self.assertEqual(pat.match(p) is not None, True)
        self.assertEqual(pat.match(p).span(), (0,256))

    def test_pickling(self):
        import pickle
        self.pickle_test(pickle)
        import cPickle
        self.pickle_test(cPickle)

    def pickle_test(self, pickle):
        oldpat = re.compile('a(?:b|(c|e){1,2}?|d)+?(.)')
        s = pickle.dumps(oldpat)
        newpat = pickle.loads(s)
        self.assertEqual(oldpat, newpat)

    def test_constants(self):
        self.assertEqual(re.I, re.IGNORECASE)
        self.assertEqual(re.L, re.LOCALE)
        self.assertEqual(re.M, re.MULTILINE)
        self.assertEqual(re.S, re.DOTALL)
        self.assertEqual(re.X, re.VERBOSE)

    def test_flags(self):
        for flag in [re.I, re.M, re.X, re.S, re.L]:
            self.assertNotEqual(re.compile('^pattern$', flag), None)

    def test_sre_character_literals(self):
        for i in [0, 8, 16, 32, 64, 127, 128, 255]:
            self.assertNotEqual(re.match(r"\%03o" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"\%03o0" % i, chr(i)+"0"), None)
            self.assertNotEqual(re.match(r"\%03o8" % i, chr(i)+"8"), None)
            self.assertNotEqual(re.match(r"\x%02x" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"\x%02x0" % i, chr(i)+"0"), None)
            self.assertNotEqual(re.match(r"\x%02xz" % i, chr(i)+"z"), None)
        self.assertRaises(re.error, re.match, "\911", "")

    def test_sre_character_class_literals(self):
        for i in [0, 8, 16, 32, 64, 127, 128, 255]:
            self.assertNotEqual(re.match(r"[\%03o]" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"[\%03o0]" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"[\%03o8]" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"[\x%02x]" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"[\x%02x0]" % i, chr(i)), None)
            self.assertNotEqual(re.match(r"[\x%02xz]" % i, chr(i)), None)
        self.assertRaises(re.error, re.match, "[\911]", "")

    def test_bug_113254(self):
        self.assertEqual(re.match(r'(a)|(b)', 'b').start(1), -1)
        self.assertEqual(re.match(r'(a)|(b)', 'b').end(1), -1)
        self.assertEqual(re.match(r'(a)|(b)', 'b').span(1), (-1, -1))

    def test_bug_527371(self):
        # bug described in patches 527371/672491
        self.assertEqual(re.match(r'(a)?a','a').lastindex, None)
        self.assertEqual(re.match(r'(a)(b)?b','ab').lastindex, 1)
        self.assertEqual(re.match(r'(?P<a>a)(?P<b>b)?b','ab').lastgroup, 'a')
        self.assertEqual(re.match("(?P<a>a(b))", "ab").lastgroup, 'a')
        self.assertEqual(re.match("((a))", "a").lastindex, 1)

    def test_bug_545855(self):
        # bug 545855 -- This pattern failed to cause a compile error as it
        # should, instead provoking a TypeError.
        self.assertRaises(re.error, re.compile, 'foo[a-')

    def test_bug_418626(self):
        # bugs 418626 at al. -- Testing Greg Chapman's addition of op code
        # SRE_OP_MIN_REPEAT_ONE for eliminating recursion on simple uses of
        # pattern '*?' on a long string.
        self.assertEqual(re.match('.*?c', 10000*'ab'+'cd').end(0), 20001)
        self.assertEqual(re.match('.*?cd', 5000*'ab'+'c'+5000*'ab'+'cde').end(0),
                         20003)
        self.assertEqual(re.match('.*?cd', 20000*'abc'+'de').end(0), 60001)
        # non-simple '*?' still used to hit the recursion limit, before the
        # non-recursive scheme was implemented.
        self.assertEqual(re.search('(a|b)*?c', 10000*'ab'+'cd').end(0), 20001)

    def test_bug_612074(self):
        pat=u"["+re.escape(u"\u2039")+u"]"
        self.assertEqual(re.compile(pat) and 1, 1)

    def test_stack_overflow(self):
        # nasty cases that used to overflow the straightforward recursive
        # implementation of repeated groups.
        self.assertEqual(re.match('(x)*', 50000*'x').group(1), 'x')
        self.assertEqual(re.match('(x)*y', 50000*'x'+'y').group(1), 'x')
        self.assertEqual(re.match('(x)*?y', 50000*'x'+'y').group(1), 'x')

    def test_scanner(self):
        def s_ident(scanner, token): return token
        def s_operator(scanner, token): return "op%s" % token
        def s_float(scanner, token): return float(token)
        def s_int(scanner, token): return int(token)

        scanner = Scanner([
            (r"[a-zA-Z_]\w*", s_ident),
            (r"\d+\.\d*", s_float),
            (r"\d+", s_int),
            (r"=|\+|-|\*|/", s_operator),
            (r"\s+", None),
            ])

        self.assertNotEqual(scanner.scanner.scanner("").pattern, None)

        self.assertEqual(scanner.scan("sum = 3*foo + 312.50 + bar"),
                         (['sum', 'op=', 3, 'op*', 'foo', 'op+', 312.5,
                           'op+', 'bar'], ''))

    def test_bug_448951(self):
        # bug 448951 (similar to 429357, but with single char match)
        # (Also test greedy matches.)
        for op in '','?','*':
            self.assertEqual(re.match(r'((.%s):)?z'%op, 'z').groups(),
                             (None, None))
            self.assertEqual(re.match(r'((.%s):)?z'%op, 'a:z').groups(),
                             ('a:', 'a'))

    def test_bug_725106(self):
        # capturing groups in alternatives in repeats
        self.assertEqual(re.match('^((a)|b)*', 'abc').groups(),
                         ('b', 'a'))
        self.assertEqual(re.match('^(([ab])|c)*', 'abc').groups(),
                         ('c', 'b'))
        self.assertEqual(re.match('^((d)|[ab])*', 'abc').groups(),
                         ('b', None))
        self.assertEqual(re.match('^((a)c|[ab])*', 'abc').groups(),
                         ('b', None))
        self.assertEqual(re.match('^((a)|b)*?c', 'abc').groups(),
                         ('b', 'a'))
        self.assertEqual(re.match('^(([ab])|c)*?d', 'abcd').groups(),
                         ('c', 'b'))
        self.assertEqual(re.match('^((d)|[ab])*?c', 'abc').groups(),
                         ('b', None))
        self.assertEqual(re.match('^((a)c|[ab])*?c', 'abc').groups(),
                         ('b', None))

    def test_bug_725149(self):
        # mark_stack_base restoring before restoring marks
        self.assertEqual(re.match('(a)(?:(?=(b)*)c)*', 'abb').groups(),
                         ('a', None))
        self.assertEqual(re.match('(a)((?!(b)*))*', 'abb').groups(),
                         ('a', None, None))

    def test_bug_764548(self):
        # bug 764548, re.compile() barfs on str/unicode subclasses
        try:
            unicode
        except NameError:
            return  # no problem if we have no unicode
        class my_unicode(unicode): pass
        pat = re.compile(my_unicode("abc"))
        self.assertEqual(pat.match("xyz"), None)

    def test_finditer(self):
        iter = re.finditer(r":+", "a:b::c:::d")
        self.assertEqual([item.group(0) for item in iter],
                         [":", "::", ":::"])

    def test_bug_926075(self):
        try:
            unicode
        except NameError:
            return # no problem if we have no unicode
        self.assert_(re.compile('bug_926075') is not
                     re.compile(eval("u'bug_926075'")))

    def test_bug_931848(self):
        try:
            unicode
        except NameError:
            pass
        pattern = eval('u"[\u002E\u3002\uFF0E\uFF61]"')
        self.assertEqual(re.compile(pattern).split("a.b.c"),
                         ['a','b','c'])

    def test_bug_581080(self):
        iter = re.finditer(r"\s", "a b")
        self.assertEqual(iter.next().span(), (1,2))
        self.assertRaises(StopIteration, iter.next)

        scanner = re.compile(r"\s").scanner("a b")
        self.assertEqual(scanner.search().span(), (1, 2))
        self.assertEqual(scanner.search(), None)

    def test_bug_817234(self):
        iter = re.finditer(r".*", "asdf")
        self.assertEqual(iter.next().span(), (0, 4))
        self.assertEqual(iter.next().span(), (4, 4))
        self.assertRaises(StopIteration, iter.next)


def run_re_tests():
    from test.re_tests import benchmarks, tests, SUCCEED, FAIL, SYNTAX_ERROR
    if verbose:
        print 'Running re_tests test suite'
    else:
        # To save time, only run the first and last 10 tests
        #tests = tests[:10] + tests[-10:]
        pass

    for t in tests:
        sys.stdout.flush()
        pattern = s = outcome = repl = expected = None
        if len(t) == 5:
            pattern, s, outcome, repl, expected = t
        elif len(t) == 3:
            pattern, s, outcome = t
        else:
            raise ValueError, ('Test tuples should have 3 or 5 fields', t)

        try:
            obj = re.compile(pattern)
        except re.error:
            if outcome == SYNTAX_ERROR: pass  # Expected a syntax error
            else:
                print '=== Syntax error:', t
        except KeyboardInterrupt: raise KeyboardInterrupt
        except:
            print '*** Unexpected error ***', t
            if verbose:
                traceback.print_exc(file=sys.stdout)
        else:
            try:
                result = obj.search(s)
            except re.error, msg:
                print '=== Unexpected exception', t, repr(msg)
            if outcome == SYNTAX_ERROR:
                # This should have been a syntax error; forget it.
                pass
            elif outcome == FAIL:
                if result is None: pass   # No match, as expected
                else: print '=== Succeeded incorrectly', t
            elif outcome == SUCCEED:
                if result is not None:
                    # Matched, as expected, so now we compute the
                    # result string and compare it to our expected result.
                    start, end = result.span(0)
                    vardict={'found': result.group(0),
                             'groups': result.group(),
                             'flags': result.re.flags}
                    for i in range(1, 100):
                        try:
                            gi = result.group(i)
                            # Special hack because else the string concat fails:
                            if gi is None:
                                gi = "None"
                        except IndexError:
                            gi = "Error"
                        vardict['g%d' % i] = gi
                    for i in result.re.groupindex.keys():
                        try:
                            gi = result.group(i)
                            if gi is None:
                                gi = "None"
                        except IndexError:
                            gi = "Error"
                        vardict[i] = gi
                    repl = eval(repl, vardict)
                    if repl != expected:
                        print '=== grouping error', t,
                        print repr(repl) + ' should be ' + repr(expected)
                else:
                    print '=== Failed incorrectly', t

                # Try the match on a unicode string, and check that it
                # still succeeds.
                try:
                    result = obj.search(unicode(s, "latin-1"))
                    if result is None:
                        print '=== Fails on unicode match', t
                except NameError:
                    continue # 1.5.2
                except TypeError:
                    continue # unicode test case

                # Try the match on a unicode pattern, and check that it
                # still succeeds.
                obj=re.compile(unicode(pattern, "latin-1"))
                result = obj.search(s)
                if result is None:
                    print '=== Fails on unicode pattern match', t

                # Try the match with the search area limited to the extent
                # of the match and see if it still succeeds.  \B will
                # break (because it won't match at the end or start of a
                # string), so we'll ignore patterns that feature it.

                if pattern[:2] != '\\B' and pattern[-2:] != '\\B' \
                               and result is not None:
                    obj = re.compile(pattern)
                    result = obj.search(s, result.start(0), result.end(0) + 1)
                    if result is None:
                        print '=== Failed on range-limited match', t

                # Try the match with IGNORECASE enabled, and check that it
                # still succeeds.
                obj = re.compile(pattern, re.IGNORECASE)
                result = obj.search(s)
                if result is None:
                    print '=== Fails on case-insensitive match', t

                # Try the match with LOCALE enabled, and check that it
                # still succeeds.
                obj = re.compile(pattern, re.LOCALE)
                result = obj.search(s)
                if result is None:
                    print '=== Fails on locale-sensitive match', t

                # Try the match with UNICODE locale enabled, and check
                # that it still succeeds.
                obj = re.compile(pattern, re.UNICODE)
                result = obj.search(s)
                if result is None:
                    print '=== Fails on unicode-sensitive match', t

def test_main():
    run_unittest(ReTests)
    run_re_tests()

if __name__ == "__main__":
    test_main()