# -*- Mode: Python -*-
# GObject-Introspection - a framework for introspecting GObject libraries
# Copyright (C) 2012 Dieter Verfaillie <dieterv@optionexplicit.be>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#

'''
test_patterns.py

Tests ensuring the regular expression programs used
in annotationparser.py continue to function correctly.
Each regular expression program is tested on input that
should not match and input that should match. When input
should match, resulting symbolic groups are verified
against the expected output.
'''

import unittest

from giscanner.annotationparser import (COMMENT_BLOCK_START_RE, COMMENT_BLOCK_END_RE,
                                        COMMENT_ASTERISK_RE, INDENTATION_RE, EMPTY_LINE_RE,
                                        SECTION_RE, SYMBOL_RE, PROPERTY_RE, ACTION_RE,
                                        SIGNAL_RE, PARAMETER_RE, TAG_RE,
                                        TAG_VALUE_VERSION_RE, TAG_VALUE_STABILITY_RE)


comment_start_tests = [
    (COMMENT_BLOCK_START_RE, '/**',
         {'code': '',
          'token': '/**',
          'comment': ''}),
    (COMMENT_BLOCK_START_RE, '   /**',
         {'code': '',
          'token': '/**',
          'comment': ''}),
    (COMMENT_BLOCK_START_RE, ' /** ',
         {'code': '',
          'token': '/**',
          'comment': ''}),
    (COMMENT_BLOCK_START_RE, 'xyz /** ',
         {'code': 'xyz',
          'token': '/**',
          'comment': ''}),
    (COMMENT_BLOCK_START_RE, '    xyz    /** ',
         {'code': '    xyz',
          'token': '/**',
          'comment': ''}),
    (COMMENT_BLOCK_START_RE, '/** xyz',
         {'code': '',
          'token': '/**',
          'comment': 'xyz'}),
    (COMMENT_BLOCK_START_RE, ' /**xyz',
         {'code': '',
          'token': '/**',
          'comment': 'xyz'}),
    (COMMENT_BLOCK_START_RE, ' /** xyz',
         {'code': '',
          'token': '/**',
          'comment': 'xyz'}),
    (COMMENT_BLOCK_START_RE, '/***',
         None),
    (COMMENT_BLOCK_START_RE, ' /***',
         None),
    (COMMENT_BLOCK_START_RE, ' /*** ',
         None),
    (COMMENT_BLOCK_START_RE, '/*** xyz',
         None),
    (COMMENT_BLOCK_START_RE, '/***** xyz',
         None),
    (COMMENT_BLOCK_START_RE, ' /*****xyz',
         None),
    (COMMENT_BLOCK_START_RE, ' /**/',
         None),
]


comment_end_tests = [
    (COMMENT_BLOCK_END_RE, '*/',
         {'comment': '',
          'token': '*/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, '   */',
         {'comment': '',
          'token': '*/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, ' */ ',
         {'comment': '',
          'token': '*/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, '*/xyz',
         {'comment': '',
          'token': '*/',
          'code': 'xyz'}),
    (COMMENT_BLOCK_END_RE, '   */xyz',
         {'comment': '',
          'token': '*/',
          'code': 'xyz'}),
    (COMMENT_BLOCK_END_RE, ' */ xyz',
         {'comment': '',
          'token': '*/',
          'code': ' xyz'}),
    (COMMENT_BLOCK_END_RE, '**/',
         {'comment': '',
          'token': '**/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, ' **/',
         {'comment': '',
          'token': '**/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, ' **/ ',
         {'comment': '',
          'token': '**/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, 'test */',
         {'comment': 'test',
          'token': '*/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, ' test*/',
         {'comment': 'test',
          'token': '*/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, 'test */ xyz',
         {'comment': 'test',
          'token': '*/',
          'code': ' xyz'}),
    (COMMENT_BLOCK_END_RE, ' test*/  xyz  ',
         {'comment': 'test',
          'token': '*/',
          'code': '  xyz'}),
    (COMMENT_BLOCK_END_RE, 'test **/',
         {'comment': 'test',
          'token': '**/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, ' test**/',
         {'comment': 'test',
          'token': '**/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, 'test *****/',
         {'comment': 'test',
          'token': '*****/',
          'code': ''}),
    (COMMENT_BLOCK_END_RE, ' test*****/',
         {'comment': 'test',
          'token': '*****/',
          'code': ''})]


comment_asterisk_tests = [
    (COMMENT_ASTERISK_RE, '*',
         {'comment': ''}),
    (COMMENT_ASTERISK_RE, '* ',
         {'comment': ''}),
    (COMMENT_ASTERISK_RE, ' *',
         {'comment': ''}),
    (COMMENT_ASTERISK_RE, ' * ',
         {'comment': ''}),
    (COMMENT_ASTERISK_RE, '    *    ',
         {'comment': ''}),
    (COMMENT_ASTERISK_RE, '    *    test',
         {'comment': ''}),
    (COMMENT_ASTERISK_RE, 'test    *    ',
         {'comment': 'test'})]


indentaton_tests = [
    (INDENTATION_RE, '',
         {'indentation': ''}),
    (INDENTATION_RE, ' ',
         {'indentation': ' '}),
    (INDENTATION_RE, '    ',
         {'indentation': '    '}),
    (INDENTATION_RE, '    x',
         {'indentation': '    '}),
    (INDENTATION_RE, '    *',
         {'indentation': '    '})]


empty_line_tests = [
    (EMPTY_LINE_RE, '',
         {}),
    (EMPTY_LINE_RE, ' ',
         {}),
    (EMPTY_LINE_RE, ' .',
         None)]


identifier_section_tests = [
    (SECTION_RE, 'TSIEOCN',
         None),
    (SECTION_RE, 'section',
         None),
    (SECTION_RE, 'section:',
         None),
    (SECTION_RE, 'section:test',
         None),
    (SECTION_RE, 'SECTION',
         None),
    (SECTION_RE, 'SECTION  \t   ',
         None),
    (SECTION_RE, '   \t  SECTION  \t   ',
         None),
    (SECTION_RE, 'SECTION:   \t ',
         None),
    (SECTION_RE, 'SECTION   :   ',
         None),
    (SECTION_RE, '   SECTION : ',
         None),
    (SECTION_RE, 'SECTION:gtkwidget',
         {'delimiter': ':',
          'section_name': 'gtkwidget'}),
    (SECTION_RE, 'SECTION:gtkwidget  ',
         {'delimiter': ':',
          'section_name': 'gtkwidget'}),
    (SECTION_RE, '  SECTION:gtkwidget',
         {'delimiter': ':',
          'section_name': 'gtkwidget'}),
    (SECTION_RE, '  SECTION:gtkwidget\t  ',
         {'delimiter': ':',
          'section_name': 'gtkwidget'}),
    (SECTION_RE, 'SECTION:    gtkwidget   ',
         {'delimiter': ':',
          'section_name': 'gtkwidget'}),
    (SECTION_RE, 'SECTION   :  gtkwidget',
         {'delimiter': ':',
          'section_name': 'gtkwidget'}),
    (SECTION_RE, 'SECTION    gtkwidget \f  ',
         {'delimiter': '',
          'section_name': 'gtkwidget'})]

identifier_symbol_tests = [
    (SYMBOL_RE, 'GBaseFinalizeFunc:',
         {'delimiter': ':',
          'symbol_name': 'GBaseFinalizeFunc',
          'fields': ''}),
    (SYMBOL_RE, 'gtk_widget_show  ',
         {'delimiter': '',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, '  gtk_widget_show',
         {'delimiter': '',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, '  gtk_widget_show  ',
         {'delimiter': '',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, 'gtk_widget_show:',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, 'gtk_widget_show :',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, 'gtk_widget_show:  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, 'gtk_widget_show :  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, '  gtk_widget_show:',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, '  gtk_widget_show :',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, '  gtk_widget_show:  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, '  gtk_widget_show :  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': ''}),
    (SYMBOL_RE, 'gtk_widget_show:(skip):',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, 'gtk_widget_show (skip)',
         {'delimiter': '',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, 'gtk_widget_show: (skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, 'gtk_widget_show : (skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, 'gtk_widget_show:  (skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, 'gtk_widget_show :  (skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, '  gtk_widget_show:(skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, '  gtk_widget_show :(skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, '  gtk_widget_show:  (skip)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, '  gtk_widget_show :  (skip)    \t    ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, '  gtk_widget_show  :  (skip)   \t    ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)'}),
    (SYMBOL_RE, 'gtk_widget_show:(skip)(test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)(test1)'}),
    (SYMBOL_RE, 'gtk_widget_show (skip)(test1)',
         {'delimiter': '',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)(test1)'}),
    (SYMBOL_RE, 'gtk_widget_show: (skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, 'gtk_widget_show : (skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, 'gtk_widget_show:  (skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, 'gtk_widget_show :  (skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, '  gtk_widget_show:(skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, '  gtk_widget_show :(skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, '  gtk_widget_show:  (skip) (test1)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, '  gtk_widget_show :  (skip) (test1)  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1)'}),
    (SYMBOL_RE, 'gtk_widget_show: (skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, 'gtk_widget_show : (skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, 'gtk_widget_show:  (skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, 'gtk_widget_show :  (skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, '  gtk_widget_show:(skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, '  gtk_widget_show :(skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, '  gtk_widget_show:  (skip) (test1) (test-2)',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, '  gtk_widget_show :  (skip) (test1) (test-2)  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip) (test1) (test-2)'}),
    (SYMBOL_RE, '  gtk_widget_show  :  (skip)  (test1)  (test-2)  ',
         {'delimiter': ':',
          'symbol_name': 'gtk_widget_show',
          'fields': '(skip)  (test1)  (test-2)'}),
    # constants
    (SYMBOL_RE, 'MY_CONSTANT:',
         {'delimiter': ':',
          'symbol_name': 'MY_CONSTANT',
          'fields': ''}),
    # structs
    (SYMBOL_RE, 'FooWidget:',
         {'delimiter': ':',
          'symbol_name': 'FooWidget',
          'fields': ''}),
    # enums
    (SYMBOL_RE, 'Something:',
         {'delimiter': ':',
          'symbol_name': 'Something',
          'fields': ''}),
    # annotations with multiple closing parentheses
    (SYMBOL_RE, 'FooWidget: (transfer full) (type GLib.List(utf8))',
         {'delimiter': ':',
          'symbol_name': 'FooWidget',
          'fields': '(transfer full) (type GLib.List(utf8))'}),
    (SYMBOL_RE, 'FooWidget: (transfer full) (type GLib.List((utf8)))',
         {'delimiter': ':',
          'symbol_name': 'FooWidget',
          'fields': '(transfer full) (type GLib.List((utf8)))'}),
    (SYMBOL_RE, 'FooWidget: (transfer full) (type GLib.List(GLib.List(utf8)))',
         {'delimiter': ':',
          'symbol_name': 'FooWidget',
          'fields': '(transfer full) (type GLib.List(GLib.List(utf8)))'}),
    (SYMBOL_RE, 'FooWidget: (type GLib.List(GLib.List(utf8))) (transfer full)',
         {'delimiter': ':',
          'symbol_name': 'FooWidget',
          'fields': '(type GLib.List(GLib.List(utf8))) (transfer full)'}),
    (SYMBOL_RE, 'FooWidget: (type GLib.List(GLib.List(utf8)))(transfer full)(type GLib.List(GLib.List(utf8)))',
         {'delimiter': ':',
          'symbol_name': 'FooWidget',
          'fields': '(type GLib.List(GLib.List(utf8)))(transfer full)(type GLib.List(GLib.List(utf8)))'})]

identifier_property_tests = [
    # simple property name
    (PROPERTY_RE, 'GtkWidget:name (skip)',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': '',
          'fields': '(skip)'}),
    (PROPERTY_RE, 'GtkWidget:name',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, ' GtkWidget :name',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, 'GtkWidget: name ',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, '  GtkWidget  :  name  ',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, 'GtkWidget:name:',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'GtkWidget:name:  ',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, '  GtkWidget:name:',
         {'class_name': 'GtkWidget',
          'property_name': 'name',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'Something:name:',
         {'class_name': 'Something',
          'property_name': 'name',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'Something:name:  ',
         {'class_name': 'Something',
          'property_name': 'name',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, '  Something:name:',
         {'class_name': 'Something',
          'property_name': 'name',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'Weird-thing:name:',
         None),
    (PROPERTY_RE, 'really-weird_thing:name:',
         None),
    (PROPERTY_RE, 'GWin32InputStream:handle:',
         {'class_name': 'GWin32InputStream',
          'property_name': 'handle',
          'delimiter': ':',
          'fields': ''}),
    # properties: property name that contains a dash
    (PROPERTY_RE, 'GtkWidget:double-buffered (skip)',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': '',
          'fields': '(skip)'}),
    (PROPERTY_RE, 'GtkWidget:double-buffered',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, ' GtkWidget :double-buffered',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, 'GtkWidget: double-buffered ',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, '  GtkWidget  :  double-buffered  ',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': '',
          'fields': ''}),
    (PROPERTY_RE, 'GtkWidget:double-buffered:',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'GtkWidget:double-buffered:  ',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, '  GtkWidget:double-buffered:',
         {'class_name': 'GtkWidget',
          'property_name': 'double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'Something:double-buffered:',
         {'class_name': 'Something',
          'property_name': 'double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'Something:double-buffered:  ',
         {'class_name': 'Something',
          'property_name': 'double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, '  Something:double-buffered:',
         {'class_name': 'Something',
          'property_name': 'double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (PROPERTY_RE, 'Weird-thing:double-buffered:',
         None),
    (PROPERTY_RE, 'really-weird_thing:double-buffered:',
         None),
    (PROPERTY_RE, ' GMemoryOutputStream:realloc-function: (skip)',
         {'class_name': 'GMemoryOutputStream',
          'property_name': 'realloc-function',
          'delimiter': ':',
          'fields': '(skip)'}),
    # properties: data with multiple closing parentheses
    (PROPERTY_RE, 'GMemoryOutputStream:realloc-function: (transfer full) (type GLib.List(utf8))',
         {'class_name': 'GMemoryOutputStream',
          'property_name': 'realloc-function',
          'delimiter': ':',
          'fields': '(transfer full) (type GLib.List(utf8))'}),
    (PROPERTY_RE, 'GMemoryOutputStream:realloc-function: (transfer full) (type GLib.List((utf8)))',
         {'class_name': 'GMemoryOutputStream',
          'property_name': 'realloc-function',
          'delimiter': ':',
          'fields': '(transfer full) (type GLib.List((utf8)))'}),
    (PROPERTY_RE, 'GMemoryOutputStream:realloc-function: (transfer full) (type GLib.List(GLib.List(utf8)))',
         {'class_name': 'GMemoryOutputStream',
          'property_name': 'realloc-function',
          'delimiter': ':',
          'fields': '(transfer full) (type GLib.List(GLib.List(utf8)))'}),
    (PROPERTY_RE, 'GMemoryOutputStream:realloc-function: (type GLib.List(GLib.List(utf8))) (transfer full)',
         {'class_name': 'GMemoryOutputStream',
          'property_name': 'realloc-function',
          'delimiter': ':',
          'fields': '(type GLib.List(GLib.List(utf8))) (transfer full)'})]

identifier_signal_tests = [
    # simple signal name
    (SIGNAL_RE, 'GtkWidget::changed: (skip)',
         {'class_name': 'GtkWidget',
          'signal_name': 'changed',
          'delimiter': ':',
          'fields': '(skip)'}),
    (SIGNAL_RE, 'GtkWidget::changed:',
         {'class_name': 'GtkWidget',
          'signal_name': 'changed',
          'delimiter': ':',
          'fields': ''}),
    (SIGNAL_RE, 'Something::changed:',
         {'class_name': 'Something',
          'signal_name': 'changed',
          'delimiter': ':',
          'fields': ''}),
    (SIGNAL_RE, 'Weird-thing::changed:',
         None),
    (SIGNAL_RE, 'really-weird_thing::changed:',
         None),
    # signals: signal name that contains a dash
    (SIGNAL_RE, 'GtkWidget::hierarchy-changed: (skip)',
         {'class_name': 'GtkWidget',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': '(skip)'}),
    (SIGNAL_RE, 'GtkWidget::hierarchy-changed:',
         {'class_name': 'GtkWidget',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': ''}),
    (SIGNAL_RE, 'Something::hierarchy-changed:',
         {'class_name': 'Something',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': ''}),
    (SIGNAL_RE, 'Weird-thing::hierarchy-changed:',
         None),
    (SIGNAL_RE, 'really-weird_thing::hierarchy-changed:',
         None),
    # signals: data with multiple closing parentheses
    (SIGNAL_RE, 'GtkWidget::hierarchy-changed: (transfer full) (type GLib.List(utf8))',
         {'class_name': 'GtkWidget',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': '(transfer full) (type GLib.List(utf8))'}),
    (SIGNAL_RE, 'GtkWidget::hierarchy-changed: (transfer full) (type GLib.List((utf8)))',
         {'class_name': 'GtkWidget',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': '(transfer full) (type GLib.List((utf8)))'}),
    (SIGNAL_RE, 'GtkWidget::hierarchy-changed: (transfer full) (type GLib.List(GLib.List(utf8)))',
         {'class_name': 'GtkWidget',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': '(transfer full) (type GLib.List(GLib.List(utf8)))'}),
    (SIGNAL_RE, 'GtkWidget::hierarchy-changed: (type GLib.List(GLib.List(utf8))) (transfer full)',
         {'class_name': 'GtkWidget',
          'signal_name': 'hierarchy-changed',
          'delimiter': ':',
          'fields': '(type GLib.List(GLib.List(utf8))) (transfer full)'})]

identifier_action_tests = [
    (ACTION_RE, 'GtkWidget|group.action (skip)',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': '',
          'fields': '(skip)'}),
    (ACTION_RE, 'GtkWidget|group.action',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, ' GtkWidget |group.action',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'GtkWidget| group.action ',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, '  GtkWidget  |  group.action  ',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'GtkWidget|group.action:',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'GtkWidget|group.action:  ',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, '  GtkWidget|group.action:',
         {'class_name': 'GtkWidget',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'Something|group.action:',
         {'class_name': 'Something',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'Something|group.action:  ',
         {'class_name': 'Something',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, '  Something|group.action:',
         {'class_name': 'Something',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'Weird-thing|name:',
         None),
    (ACTION_RE, 'really-weird_thing|name:',
         None),
    (ACTION_RE, 'GWin32InputStream|group.action:',
         {'class_name': 'GWin32InputStream',
          'action_name': 'group.action',
          'delimiter': ':',
          'fields': ''}),
    # properties: action name that contains a dash
    (ACTION_RE, 'GtkWidget|group.double-buffered (skip)',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': '',
          'fields': '(skip)'}),
    (ACTION_RE, 'GtkWidget|group.double-buffered',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, ' GtkWidget |group.double-buffered',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'GtkWidget| group.double-buffered ',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, '  GtkWidget  |  group.double-buffered  ',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'GtkWidget|group.double-buffered:',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'GtkWidget|group.double-buffered:  ',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, '  GtkWidget|group.double-buffered:',
         {'class_name': 'GtkWidget',
          'action_name': 'group.double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'Something|group.double-buffered:',
         {'class_name': 'Something',
          'action_name': 'group.double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'Something|group.double-buffered:  ',
         {'class_name': 'Something',
          'action_name': 'group.double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, '  Something|group.double-buffered:',
         {'class_name': 'Something',
          'action_name': 'group.double-buffered',
          'delimiter': ':',
          'fields': ''}),
    (ACTION_RE, 'Weird-thing|double-buffered:',
         None),
    (ACTION_RE, 'really-weird_thing|double-buffered:',
         None),
    (ACTION_RE, ' GMemoryOutputStream|group.realloc-function: (skip)',
         {'class_name': 'GMemoryOutputStream',
          'action_name': 'group.realloc-function',
          'delimiter': ':',
          'fields': '(skip)'}),
    (ACTION_RE, 'Something|group-double.double-buffered',
         {'class_name': 'Something',
          'action_name': 'group-double.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'Something| group-double.double-buffered',
         {'class_name': 'Something',
          'action_name': 'group-double.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'Something |group-double.double-buffered',
         {'class_name': 'Something',
          'action_name': 'group-double.double-buffered',
          'delimiter': '',
          'fields': ''}),
    (ACTION_RE, 'Something | group-double.double-buffered',
         {'class_name': 'Something',
          'action_name': 'group-double.double-buffered',
          'delimiter': '',
          'fields': ''})]

parameter_tests = [
    (PARAMETER_RE, '@Short_description: Base class for all widgets  ',
         {'parameter_name': 'Short_description',
          'fields': 'Base class for all widgets'}),
    (PARAMETER_RE, '@...: the value of the first property, followed optionally by more',
         {'parameter_name': '...',
          'fields': 'the value of the first property, followed optionally by more'}),
    (PARAMETER_RE, '@args...: list of arguments',
         {'parameter_name': 'args...',
          'fields': 'list of arguments'}),
    (PARAMETER_RE, '@widget: a #GtkWidget',
         {'parameter_name': 'widget',
          'fields': 'a #GtkWidget'}),
    (PARAMETER_RE, '@widget_pointer: (inout) (transfer none): '
                   'address of a variable that contains @widget',
         {'parameter_name': 'widget_pointer',
          'fields': '(inout) (transfer none): address of a variable that contains @widget'}),
    (PARAMETER_RE, '@weird_thing: (inout) (transfer none) (allow-none) (attribute) (destroy) '
                   '(foreign) (inout) (out) (transfer) (skip) (method): some weird @thing',
         {'parameter_name': 'weird_thing',
          'fields': '(inout) (transfer none) (allow-none) (attribute) (destroy) '
                  '(foreign) (inout) (out) (transfer) (skip) (method): '
                  'some weird @thing'}),
    (PARAMETER_RE, '@data: a pointer to the element data. The data may be moved as elements '
                   'are added to the #GByteArray.',
         {'parameter_name': 'data',
          'fields': 'a pointer to the element data. The data may be moved as elements '
                            'are added to the #GByteArray.'}),
    (PARAMETER_RE, '@a: a #GSequenceIter',
         {'parameter_name': 'a',
          'fields': 'a #GSequenceIter'}),
    (PARAMETER_RE, '@keys: (array length=n_keys) (element-type GQuark) (allow-none):',
         {'parameter_name': 'keys',
          'fields': '(array length=n_keys) (element-type GQuark) (allow-none):'}),
    # parentheses in description
    (PARAMETER_RE, '@foo: This is a foo (be careful using it)',
         {'parameter_name': 'foo',
          'fields': 'This is a foo (be careful using it)'}),
    (PARAMETER_RE, '@foo: This is a foo (be careful using it) and see also: bar',
         {'parameter_name': 'foo',
          'fields': 'This is a foo (be careful using it) and see also: bar'}),
    (PARAMETER_RE, '@foo: This is a foo (be careful using it) and see also: bar and baz',
         {'parameter_name': 'foo',
          'fields': 'This is a foo (be careful using it) and see also: bar and baz'}),
    # annotations with multiple closing parentheses, combined with parentheses in description
    (PARAMETER_RE, '@foo: (transfer full) (element-type guint8): This is a foo (be careful using it) and see also: bar and baz',
         {'parameter_name': 'foo',
          'fields': '(transfer full) (element-type guint8): This is a foo (be careful using it) and see also: bar and baz'}),
    (PARAMETER_RE, '@foo: (transfer full) (element-type GLib.List(utf8)): This is a foo (be careful using it) and see also: bar and baz',
         {'parameter_name': 'foo',
          'fields': '(transfer full) (element-type GLib.List(utf8)): This is a foo (be careful using it) and see also: bar and baz'}),
    (PARAMETER_RE, '@foo: (transfer full) (element-type GLib.List((utf8))): This is a foo (be careful using it) and see also: bar and baz',
         {'parameter_name': 'foo',
          'fields': '(transfer full) (element-type GLib.List((utf8))): This is a foo (be careful using it) and see also: bar and baz'}),
    (PARAMETER_RE, '@foo: (transfer full) (element-type GLib.List(GLib.List(utf8))): This is a foo (be careful using it) and see also: bar and baz',
         {'parameter_name': 'foo',
          'fields': '(transfer full) (element-type GLib.List(GLib.List(utf8))): This is a foo (be careful using it) and see also: bar and baz'}),
    (PARAMETER_RE, '@foo: (element-type GLib.List(GLib.List(utf8))) (transfer full): This is a foo (be careful using it) and see also: bar and baz',
         {'parameter_name': 'foo',
          'fields': '(element-type GLib.List(GLib.List(utf8))) (transfer full): This is a foo (be careful using it) and see also: bar and baz'})]

tag_tests = [
    (TAG_RE, 'Since 3.0',
         None),
    (TAG_RE, 'Since: 3.0',
         {'tag_name': 'Since',
          'fields': '3.0'}),
    (TAG_RE, 'Attributes: (inout) (transfer none): some note about attributes',
         {'tag_name': 'Attributes',
          'fields': '(inout) (transfer none): some note about attributes'}),
    (TAG_RE, 'Rename to: something_else',
         {'tag_name': 'Rename to',
          'fields': 'something_else'}),
    (TAG_RE, '@Deprecated: Since 2.8, reference counting is done atomically',
         None),
    (TAG_RE, 'Returns %TRUE and does weird things',
         None),
    (TAG_RE, 'Returns: a #GtkWidget',
         {'tag_name': 'Returns',
          'fields': 'a #GtkWidget'}),
    (TAG_RE, 'Return value: (transfer none): The binary data that @text responds. '
             'This pointer',
         {'tag_name': 'Return value',
          'fields': '(transfer none): The binary data that @text responds. This pointer'}),
    (TAG_RE, 'Return value: (transfer full) (array length=out_len) (element-type guint8):',
         {'tag_name': 'Return value',
          'fields': '(transfer full) (array length=out_len) (element-type guint8):'}),
    (TAG_RE, 'Returns: A boolean value, but let me tell you a bit about this boolean.  It',
         {'tag_name': 'Returns',
          'fields': 'A boolean value, but let me tell you a bit about this boolean.  It'}),
    (TAG_RE, 'Returns: (transfer container) (element-type GObject.ParamSpec): a',
         {'tag_name': 'Returns',
          'fields': '(transfer container) (element-type GObject.ParamSpec): a'}),
    (TAG_RE, 'Return value: (type GLib.HashTable<utf8,GLib.HashTable<utf8,utf8>>) '
             '(transfer full):',
         {'tag_name': 'Return value',
          'fields': '(type GLib.HashTable<utf8,GLib.HashTable<utf8,utf8>>) (transfer full):'}),
    # parentheses in description
    (TAG_RE, 'Returns: Returns a foo (be careful using it)',
         {'tag_name': 'Returns',
          'fields': 'Returns a foo (be careful using it)'}),
    (TAG_RE, 'Returns: Returns a foo (be careful using it) and see also: bar',
         {'tag_name': 'Returns',
          'fields': 'Returns a foo (be careful using it) and see also: bar'}),
    (TAG_RE, 'Returns: Returns a foo (be careful using it) and see also: bar and baz',
         {'tag_name': 'Returns',
          'fields': 'Returns a foo (be careful using it) and see also: bar and baz'}),
    # annotations with multiple closing parentheses, combined with parentheses in description
    (TAG_RE, 'Returns: (transfer full) (element-type guint8): Returns a foo (be careful using it) and see also: bar and baz',
         {'tag_name': 'Returns',
          'fields': '(transfer full) (element-type guint8): Returns a foo (be careful using it) and see also: bar and baz'}),
    (TAG_RE, 'Returns: (transfer full) (element-type GLib.List(utf8)): Returns a foo (be careful using it) and see also: bar and baz',
         {'tag_name': 'Returns',
          'fields': '(transfer full) (element-type GLib.List(utf8)): Returns a foo (be careful using it) and see also: bar and baz'}),
    (TAG_RE, 'Returns: (transfer full) (element-type GLib.List((utf8))): Returns a foo (be careful using it) and see also: bar and baz',
         {'tag_name': 'Returns',
          'fields': '(transfer full) (element-type GLib.List((utf8))): Returns a foo (be careful using it) and see also: bar and baz'}),
    (TAG_RE, 'Returns: (transfer full) (element-type GLib.List(GLib.List(utf8))): Returns a foo (be careful using it) and see also: bar and baz',
         {'tag_name': 'Returns',
          'fields': '(transfer full) (element-type GLib.List(GLib.List(utf8))): Returns a foo (be careful using it) and see also: bar and baz'}),
    (TAG_RE, 'Returns: (element-type GLib.List(GLib.List(utf8))) (transfer full): Returns a foo (be careful using it) and see also: bar and baz',
         {'tag_name': 'Returns',
          'fields': '(element-type GLib.List(GLib.List(utf8))) (transfer full): Returns a foo (be careful using it) and see also: bar and baz'}),
    (TAG_RE, 'Returns: (element-type utf8=invalid GLib.HashTable(utf8,utf8) invalid): returns %NULL.',
         {'tag_name': 'Returns',
          'fields': '(element-type utf8=invalid GLib.HashTable(utf8,utf8) invalid): returns %NULL.'})]


tag_value_version_tests = [
    (TAG_VALUE_VERSION_RE, ' abc',
         {'value': '',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_VERSION_RE, '5 abc',
         {'value': '5',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_VERSION_RE, '5:abc',
         {'value': '5',
          'delimiter': ':',
          'description': 'abc'}),
    (TAG_VALUE_VERSION_RE, ' 0.1: abc',
         {'value': '0.1',
          'delimiter': ':',
          'description': 'abc'}),
    (TAG_VALUE_VERSION_RE, ' 12.10.3698: abc',
         {'value': '12.10.3698',
          'delimiter': ':',
          'description': 'abc'})]


tag_value_stability_tests = [
    (TAG_VALUE_STABILITY_RE, ' abc',
         {'value': '',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'stable abc',
         {'value': 'stable',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'unstable abc',
         {'value': 'unstable',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'private abc',
         {'value': 'private',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'internal abc',
         {'value': 'internal',
          'delimiter': '',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'StAbLe: abc',
         {'value': 'StAbLe',
          'delimiter': ':',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'uNsTaBlE: abc',
         {'value': 'uNsTaBlE',
          'delimiter': ':',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'PRIVATE: abc',
         {'value': 'PRIVATE',
          'delimiter': ':',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, '  internal  : abc',
         {'value': 'internal',
          'delimiter': ':',
          'description': 'abc'}),
    (TAG_VALUE_STABILITY_RE, 'xyz: abc',
         {'value': '',
          'delimiter': '',
          'description': 'xyz: abc'})]


def create_test_method(testcase):
    def do_test(self):
        (program, text, expected) = testcase

        match = program.match(text)

        if match is not None:
            msg = 'Test matched pattern but specifies no expected named groups.'
            self.assertTrue(isinstance(expected, dict), msg)

            for group in match.groupdict().keys():
                msg = 'Test case is missing expected results for named group "%s".' % (group)
                self.assertTrue(group in expected.keys(), msg)

        if expected is None:
            msg = 'Program matched text but shouldn\'t:\n"%s"'
            self.assertTrue(match is None, msg % (text, ))
        else:
            msg = 'Program should match text but didn\'t:\n"%s"'
            self.assertTrue(match is not None, msg % (text, ))

            for key, value in expected.items():
                msg = 'expected "%s" for "%s" but match returned "%s"'
                msg = msg % (value, key, match.group(key))
                self.assertEqual(match.group(key), value, msg)

    return do_test


def create_test_case(tests_class_name, testcases):
    test_methods = {}
    for counter, test in enumerate(testcases):
        test_name = 'test_%03d' % (counter + 1)
        test_method = create_test_method(test)
        test_method.__name__ = test_name
        test_methods[test_name] = test_method

    return type(tests_class_name, (unittest.TestCase,), test_methods)


def create_test_cases():
    test_cases = {}
    for name, test_data in (('TestCommentStart', comment_start_tests),
                            ('TestCommentEnd', comment_end_tests),
                            ('TestCommentAsterisk', comment_asterisk_tests),
                            ('TestIndentaton', indentaton_tests),
                            ('TestEmptyLine', empty_line_tests),
                            ('TestIdentifierSection', identifier_section_tests),
                            ('TestIdentifierSymbol', identifier_symbol_tests),
                            ('TestIdentifierProperty', identifier_property_tests),
                            ('TestIdentifierSignal', identifier_signal_tests),
                            ('TestIdentifierAction', identifier_action_tests),
                            ('TestParameter', parameter_tests),
                            ('TestTag', tag_tests),
                            ('TestTagValueVersion', tag_value_version_tests),
                            ('TestTagValueStability', tag_value_stability_tests)):
        test_case = create_test_case(name, test_data)
        test_cases[test_case.__name__] = test_case

    return test_cases


# We currently need to push all the new test cases into the modules globals
# in order for parameterized tests to work. Ideally all that should be needed
# is the "load_tests" hook, but this does not work in the case were the tests
# are run in parameterized mode, e.g: python -m unittest test_parser.Test...
_all_tests = create_test_cases()
globals().update(_all_tests)


if __name__ == '__main__':
    unittest.main()
