diff options
Diffstat (limited to 'tests')
62 files changed, 10773 insertions, 3128 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am deleted file mode 100644 index b845e4b..0000000 --- a/tests/Makefile.am +++ /dev/null @@ -1,155 +0,0 @@ -CLEANFILES = -check_LTLIBRARIES = libgimarshallingtests.la -test_typelibs = GIMarshallingTests-1.0.typelib - -nodist_libgimarshallingtests_la_SOURCES = $(GI_DATADIR)/tests/gimarshallingtests.c $(GI_DATADIR)/tests/gimarshallingtests.h -libgimarshallingtests_la_CFLAGS = $(GLIB_CFLAGS) -libgimarshallingtests_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) - -# This is a hack to make sure a shared library is built -libgimarshallingtests.la: $(libgimarshallingtests_la_OBJECTS) $(libgimarshallingtests_la_DEPENDENCIES) - $(AM_V_GEN) $(LINK) -rpath $(pkgpyexecdir) $(libgimarshallingtests_la_LDFLAGS) $(libgimarshallingtests_la_OBJECTS) $(libgimarhallingtests_la_LIBADD) $(LIBS) - -GIMarshallingTests-1.0.gir: libgimarshallingtests.la Makefile - $(AM_V_GEN) g-ir-scanner --include=Gio-2.0 \ - --namespace=GIMarshallingTests --nsversion=1.0 --symbol-prefix=gi_marshalling_tests \ - --warn-all --warn-error \ - --library=libgimarshallingtests.la \ - --libtool="$(top_builddir)/libtool" \ - --output $@ \ - $(nodist_libgimarshallingtests_la_SOURCES) -GIMarshallingTests-1.0.typelib: GIMarshallingTests-1.0.gir Makefile - $(AM_V_GEN) g-ir-compiler $< -o $@ - -# regress.c needs cairo -if ENABLE_CAIRO -check_LTLIBRARIES += libregress.la -test_typelibs += Regress-1.0.typelib -nodist_libregress_la_SOURCES = $(GI_DATADIR)/tests/regress.c $(GI_DATADIR)/tests/regress.h -libregress_la_CFLAGS = $(GIO_CFLAGS) $(CAIRO_CFLAGS) -libregress_la_LDFLAGS = -module -avoid-version $(GIO_LIBS) $(CAIRO_LIBS) - -libregress.la: $(libregress_la_OBJECTS) $(libregress_la_DEPENDENCIES) - $(AM_V_GEN) $(LINK) -rpath $(pkgpyexecdir) $(libregress_la_LDFLAGS) $(libregress_la_OBJECTS) $(libregress_la_LIBADD) $(LIBS) - -# g-i doesn't ship these as shared libraries anymore; we build them here -Regress-1.0.gir: libregress.la Makefile - $(AM_V_GEN) g-ir-scanner --include=cairo-1.0 --include=Gio-2.0 \ - --namespace=Regress --nsversion=1.0 \ - --warn-all --warn-error \ - --library=libregress.la \ - --libtool="$(top_builddir)/libtool" \ - --output $@ \ - $(nodist_libregress_la_SOURCES) -Regress-1.0.typelib: Regress-1.0.gir Makefile - $(AM_V_GEN) g-ir-compiler $< -o $@ - -endif # ENABLE_CAIRO - - -gschemas.compiled: org.gnome.test.gschema.xml - glib-compile-schemas --targetdir=. --schema-file=$< - -CLEANFILES += Regress-1.0.gir Regress-1.0.typelib GIMarshallingTests-1.0.gir GIMarshallingTests-1.0.typelib gschemas.compiled - -check_LTLIBRARIES += testhelper.la - -testhelper_la_CFLAGS = -I$(top_srcdir)/gi/_gobject -I$(top_srcdir)/gi/_glib $(PYTHON_INCLUDES) $(GLIB_CFLAGS) -testhelper_la_LDFLAGS = -module -avoid-version -testhelper_la_LIBADD = $(GLIB_LIBS) -testhelper_la_SOURCES = \ - testhelpermodule.c \ - test-floating.c \ - test-thread.c \ - test-unknown.c - -# This is a hack to make sure a shared library is built -testhelper.la: $(testhelper_la_OBJECTS) $(testhelper_la_DEPENDENCIES) - $(AM_V_GEN) $(LINK) -rpath $(pkgpyexecdir) $(testhelper_la_LDFLAGS) $(testhelper_la_OBJECTS) $(testhelper_la_LIBADD) $(LIBS) - - -.la.so: - test -L $@ || $(LN_S) .libs/$@ $@ - - -all: $(check_LTLIBRARIES:.la=.so) - -EXTRA_DIST = \ - compathelper.py \ - runtests.py \ - runtests-windows.py \ - testmodule.py \ - test-floating.h \ - test-thread.h \ - test-unknown.h \ - te_ST@nouppera \ - org.gnome.test.gschema.xml \ - test_gio.py \ - test_glib.py \ - test_gobject.py \ - test_gtype.py \ - test_interface.py \ - test_internal_api.py \ - test_iochannel.py \ - test_mainloop.py \ - test_object_marshaling.py \ - test_option.py \ - test_properties.py \ - test_signal.py \ - test_source.py \ - test_subprocess.py \ - test_thread.py \ - test_everything.py \ - test_gi.py \ - test_gdbus.py \ - test_overrides.py \ - test_overrides_glib.py \ - test_overrides_pango.py \ - test_overrides_gdk.py \ - test_overrides_gtk.py \ - test_atoms.py \ - test_generictreemodel.py \ - test_docstring.py \ - compat_test_pygtk.py \ - gi/__init__.py \ - gi/overrides/__init__.py \ - gi/overrides/Regress.py \ - $(NULL) - -clean-local: - rm -f $(check_LTLIBRARIES:.la=.so) file.txt~ - -DBUS_LAUNCH=$(shell which dbus-launch) -RUN_TESTS_ENV_VARS= \ - PYTHONPATH=$(top_builddir):$(top_builddir)/tests:$${PYTHONPATH:+:$$PYTHONPATH} \ - LD_LIBRARY_PATH=$(builddir)/.libs:$$LD_LIBRARY_PATH \ - GI_TYPELIB_PATH=$(builddir):$$GI_TYPELIB_PATH \ - XDG_DATA_DIRS=$$XDG_DATA_DIRS:/usr/share \ - MALLOC_PERTURB_=85 \ - MALLOC_CHECK_=3 \ - G_SLICE=debug-blocks \ - TESTS_BUILDDIR=$(builddir) - -# pygtkcompat tests need to be run in a separate process as they -# clobber global name space -check-local: $(check_LTLIBRARIES:.la=.so) $(test_typelibs) gschemas.compiled - @echo " CHECK Pyflakes" - @if type pyflakes >/dev/null 2>&1; then pyflakes $(top_srcdir); else echo "skipped, pyflakes not installed"; fi - @if test -z "$$SKIP_PEP8"; then \ - echo " CHECK PEP8"; \ - if type pep8 >/dev/null 2>&1; then pep8 --ignore=E501,E123,E124 --repeat --show-source $(top_srcdir); else echo "skipped, pep8 not installed"; fi; \ - fi - export `$(DBUS_LAUNCH)` && \ - $(RUN_TESTS_ENV_VARS) $(EXEC_NAME) $(PYTHON) -Wd $(srcdir)/runtests.py; rc=$$?; \ - [ "$$rc" -ne 0 ] || [ -n "$$TEST_NAMES" ] || { TEST_NAMES=compat_test_pygtk $(RUN_TESTS_ENV_VARS) $(EXEC_NAME) $(PYTHON) -Wd -Werror::PendingDeprecationWarning -Werror::DeprecationWarning -Werror::RuntimeWarning $(srcdir)/runtests.py; rc=$$?; }; \ - kill $$DBUS_SESSION_BUS_PID; \ - exit $$rc - -check.gdb: - EXEC_NAME="gdb --args" $(MAKE) check - -check.nemiver: - EXEC_NAME="nemiver" $(MAKE) check - -check.valgrind: - EXEC_NAME="valgrind --leak-check=full --show-possibly-lost=no --suppressions=python.supp" G_DEBUG=gc-friendly $(MAKE) check diff --git a/tests/Makefile.in b/tests/Makefile.in deleted file mode 100644 index d9b217c..0000000 --- a/tests/Makefile.in +++ /dev/null @@ -1,839 +0,0 @@ -# Makefile.in generated by automake 1.13.3 from Makefile.am. -# @configure_input@ - -# Copyright (C) 1994-2013 Free Software Foundation, Inc. - -# This Makefile.in is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -@SET_MAKE@ -VPATH = @srcdir@ -am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' -am__make_running_with_option = \ - case $${target_option-} in \ - ?) ;; \ - *) echo "am__make_running_with_option: internal error: invalid" \ - "target option '$${target_option-}' specified" >&2; \ - exit 1;; \ - esac; \ - has_opt=no; \ - sane_makeflags=$$MAKEFLAGS; \ - if $(am__is_gnu_make); then \ - sane_makeflags=$$MFLAGS; \ - else \ - case $$MAKEFLAGS in \ - *\\[\ \ ]*) \ - bs=\\; \ - sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ - | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ - esac; \ - fi; \ - skip_next=no; \ - strip_trailopt () \ - { \ - flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ - }; \ - for flg in $$sane_makeflags; do \ - test $$skip_next = yes && { skip_next=no; continue; }; \ - case $$flg in \ - *=*|--*) continue;; \ - -*I) strip_trailopt 'I'; skip_next=yes;; \ - -*I?*) strip_trailopt 'I';; \ - -*O) strip_trailopt 'O'; skip_next=yes;; \ - -*O?*) strip_trailopt 'O';; \ - -*l) strip_trailopt 'l'; skip_next=yes;; \ - -*l?*) strip_trailopt 'l';; \ - -[dEDm]) skip_next=yes;; \ - -[JT]) skip_next=yes;; \ - esac; \ - case $$flg in \ - *$$target_option*) has_opt=yes; break;; \ - esac; \ - done; \ - test $$has_opt = yes -am__make_dryrun = (target_option=n; $(am__make_running_with_option)) -am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) -pkgdatadir = $(datadir)/@PACKAGE@ -pkgincludedir = $(includedir)/@PACKAGE@ -pkglibdir = $(libdir)/@PACKAGE@ -pkglibexecdir = $(libexecdir)/@PACKAGE@ -am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd -install_sh_DATA = $(install_sh) -c -m 644 -install_sh_PROGRAM = $(install_sh) -c -install_sh_SCRIPT = $(install_sh) -c -INSTALL_HEADER = $(INSTALL_DATA) -transform = $(program_transform_name) -NORMAL_INSTALL = : -PRE_INSTALL = : -POST_INSTALL = : -NORMAL_UNINSTALL = : -PRE_UNINSTALL = : -POST_UNINSTALL = : -build_triplet = @build@ -host_triplet = @host@ - -# regress.c needs cairo -@ENABLE_CAIRO_TRUE@am__append_1 = libregress.la -@ENABLE_CAIRO_TRUE@am__append_2 = Regress-1.0.typelib -subdir = tests -DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ - $(top_srcdir)/depcomp -ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 -am__aclocal_m4_deps = $(top_srcdir)/m4/as-ac-expand.m4 \ - $(top_srcdir)/m4/jhflags.m4 $(top_srcdir)/m4/libtool.m4 \ - $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ - $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ - $(top_srcdir)/m4/python.m4 $(top_srcdir)/configure.ac -am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ - $(ACLOCAL_M4) -mkinstalldirs = $(install_sh) -d -CONFIG_HEADER = $(top_builddir)/config.h -CONFIG_CLEAN_FILES = -CONFIG_CLEAN_VPATH_FILES = -libgimarshallingtests_la_LIBADD = -nodist_libgimarshallingtests_la_OBJECTS = \ - libgimarshallingtests_la-gimarshallingtests.lo -libgimarshallingtests_la_OBJECTS = \ - $(nodist_libgimarshallingtests_la_OBJECTS) -AM_V_lt = $(am__v_lt_@AM_V@) -am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) -am__v_lt_0 = --silent -am__v_lt_1 = -libgimarshallingtests_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(libgimarshallingtests_la_CFLAGS) $(CFLAGS) \ - $(libgimarshallingtests_la_LDFLAGS) $(LDFLAGS) -o $@ -libregress_la_LIBADD = -@ENABLE_CAIRO_TRUE@nodist_libregress_la_OBJECTS = \ -@ENABLE_CAIRO_TRUE@ libregress_la-regress.lo -libregress_la_OBJECTS = $(nodist_libregress_la_OBJECTS) -libregress_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ - $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libregress_la_CFLAGS) \ - $(CFLAGS) $(libregress_la_LDFLAGS) $(LDFLAGS) -o $@ -@ENABLE_CAIRO_TRUE@am_libregress_la_rpath = -am__DEPENDENCIES_1 = -testhelper_la_DEPENDENCIES = $(am__DEPENDENCIES_1) -am_testhelper_la_OBJECTS = testhelper_la-testhelpermodule.lo \ - testhelper_la-test-floating.lo testhelper_la-test-thread.lo \ - testhelper_la-test-unknown.lo -testhelper_la_OBJECTS = $(am_testhelper_la_OBJECTS) -testhelper_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ - $(LIBTOOLFLAGS) --mode=link $(CCLD) $(testhelper_la_CFLAGS) \ - $(CFLAGS) $(testhelper_la_LDFLAGS) $(LDFLAGS) -o $@ -AM_V_P = $(am__v_P_@AM_V@) -am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) -am__v_P_0 = false -am__v_P_1 = : -AM_V_GEN = $(am__v_GEN_@AM_V@) -am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) -am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = -AM_V_at = $(am__v_at_@AM_V@) -am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) -am__v_at_0 = @ -am__v_at_1 = -DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) -depcomp = $(SHELL) $(top_srcdir)/depcomp -am__depfiles_maybe = depfiles -am__mv = mv -f -COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ - $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ - $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ - $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ - $(AM_CFLAGS) $(CFLAGS) -AM_V_CC = $(am__v_CC_@AM_V@) -am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) -am__v_CC_0 = @echo " CC " $@; -am__v_CC_1 = -CCLD = $(CC) -LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ - $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(AM_LDFLAGS) $(LDFLAGS) -o $@ -AM_V_CCLD = $(am__v_CCLD_@AM_V@) -am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) -am__v_CCLD_0 = @echo " CCLD " $@; -am__v_CCLD_1 = -SOURCES = $(nodist_libgimarshallingtests_la_SOURCES) \ - $(nodist_libregress_la_SOURCES) $(testhelper_la_SOURCES) -DIST_SOURCES = $(testhelper_la_SOURCES) -am__can_run_installinfo = \ - case $$AM_UPDATE_INFO_DIR in \ - n|no|NO) false;; \ - *) (install-info --version) >/dev/null 2>&1;; \ - esac -am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) -# Read a list of newline-separated strings from the standard input, -# and print each of them once, without duplicates. Input order is -# *not* preserved. -am__uniquify_input = $(AWK) '\ - BEGIN { nonempty = 0; } \ - { items[$$0] = 1; nonempty = 1; } \ - END { if (nonempty) { for (i in items) print i; }; } \ -' -# Make sure the list of sources is unique. This is necessary because, -# e.g., the same source file might be shared among _SOURCES variables -# for different programs/libraries. -am__define_uniq_tagged_files = \ - list='$(am__tagged_files)'; \ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | $(am__uniquify_input)` -ETAGS = etags -CTAGS = ctags -DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) -ACLOCAL = @ACLOCAL@ -ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ -AMTAR = @AMTAR@ -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ -AR = @AR@ -AS = @AS@ -AUTOCONF = @AUTOCONF@ -AUTOHEADER = @AUTOHEADER@ -AUTOMAKE = @AUTOMAKE@ -AWK = @AWK@ -CAIRO_CFLAGS = @CAIRO_CFLAGS@ -CAIRO_LIBS = @CAIRO_LIBS@ -CC = @CC@ -CCDEPMODE = @CCDEPMODE@ -CFLAGS = @CFLAGS@ -CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ -CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ -CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ -CPP = @CPP@ -CPPFLAGS = @CPPFLAGS@ -CYGPATH_W = @CYGPATH_W@ -DATADIR = @DATADIR@ -DEFS = @DEFS@ -DEPDIR = @DEPDIR@ -DLLTOOL = @DLLTOOL@ -DSYMUTIL = @DSYMUTIL@ -DUMPBIN = @DUMPBIN@ -ECHO_C = @ECHO_C@ -ECHO_N = @ECHO_N@ -ECHO_T = @ECHO_T@ -EGREP = @EGREP@ -EXEEXT = @EXEEXT@ -FFI_CFLAGS = @FFI_CFLAGS@ -FFI_LIBS = @FFI_LIBS@ -FGREP = @FGREP@ -GENHTML = @GENHTML@ -GIO_CFLAGS = @GIO_CFLAGS@ -GIO_LIBS = @GIO_LIBS@ -GI_CFLAGS = @GI_CFLAGS@ -GI_DATADIR = @GI_DATADIR@ -GI_LIBS = @GI_LIBS@ -GLIB_CFLAGS = @GLIB_CFLAGS@ -GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ -GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ -GLIB_LIBS = @GLIB_LIBS@ -GLIB_MKENUMS = @GLIB_MKENUMS@ -GOBJECT_QUERY = @GOBJECT_QUERY@ -GREP = @GREP@ -INSTALL = @INSTALL@ -INSTALL_DATA = @INSTALL_DATA@ -INSTALL_PROGRAM = @INSTALL_PROGRAM@ -INSTALL_SCRIPT = @INSTALL_SCRIPT@ -INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ -INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ -INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ -LCOV = @LCOV@ -LD = @LD@ -LDFLAGS = @LDFLAGS@ -LIBFFI_PC = @LIBFFI_PC@ -LIBOBJS = @LIBOBJS@ -LIBS = @LIBS@ -LIBTOOL = @LIBTOOL@ -LIPO = @LIPO@ -LN_S = @LN_S@ -LTLIBOBJS = @LTLIBOBJS@ -MAKEINFO = @MAKEINFO@ -MANIFEST_TOOL = @MANIFEST_TOOL@ -MKDIR_P = @MKDIR_P@ -NM = @NM@ -NMEDIT = @NMEDIT@ -OBJDUMP = @OBJDUMP@ -OBJEXT = @OBJEXT@ -OTOOL = @OTOOL@ -OTOOL64 = @OTOOL64@ -PACKAGE = @PACKAGE@ -PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ -PACKAGE_NAME = @PACKAGE_NAME@ -PACKAGE_STRING = @PACKAGE_STRING@ -PACKAGE_TARNAME = @PACKAGE_TARNAME@ -PACKAGE_URL = @PACKAGE_URL@ -PACKAGE_VERSION = @PACKAGE_VERSION@ -PATH_SEPARATOR = @PATH_SEPARATOR@ -PKG_CONFIG = @PKG_CONFIG@ -PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ -PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ -PLATFORM = @PLATFORM@ -PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@ -PYCAIRO_LIBS = @PYCAIRO_LIBS@ -PYGOBJECT_MAJOR_VERSION = @PYGOBJECT_MAJOR_VERSION@ -PYGOBJECT_MICRO_VERSION = @PYGOBJECT_MICRO_VERSION@ -PYGOBJECT_MINOR_VERSION = @PYGOBJECT_MINOR_VERSION@ -PYTHON = @PYTHON@ -PYTHON_BASENAME = @PYTHON_BASENAME@ -PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ -PYTHON_INCLUDES = @PYTHON_INCLUDES@ -PYTHON_LIBS = @PYTHON_LIBS@ -PYTHON_LIB_LOC = @PYTHON_LIB_LOC@ -PYTHON_PLATFORM = @PYTHON_PLATFORM@ -PYTHON_PREFIX = @PYTHON_PREFIX@ -PYTHON_SO = @PYTHON_SO@ -PYTHON_VERSION = @PYTHON_VERSION@ -RANLIB = @RANLIB@ -SED = @SED@ -SET_MAKE = @SET_MAKE@ -SHELL = @SHELL@ -STRIP = @STRIP@ -THREADING_CFLAGS = @THREADING_CFLAGS@ -VERSION = @VERSION@ -WARN_CFLAGS = @WARN_CFLAGS@ -abs_builddir = @abs_builddir@ -abs_srcdir = @abs_srcdir@ -abs_top_builddir = @abs_top_builddir@ -abs_top_srcdir = @abs_top_srcdir@ -ac_ct_AR = @ac_ct_AR@ -ac_ct_CC = @ac_ct_CC@ -ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ -am__include = @am__include@ -am__leading_dot = @am__leading_dot@ -am__quote = @am__quote@ -am__tar = @am__tar@ -am__untar = @am__untar@ -bindir = @bindir@ -build = @build@ -build_alias = @build_alias@ -build_cpu = @build_cpu@ -build_os = @build_os@ -build_vendor = @build_vendor@ -builddir = @builddir@ -datadir = @datadir@ -datarootdir = @datarootdir@ -docdir = @docdir@ -dvidir = @dvidir@ -exec_prefix = @exec_prefix@ -host = @host@ -host_alias = @host_alias@ -host_cpu = @host_cpu@ -host_os = @host_os@ -host_vendor = @host_vendor@ -htmldir = @htmldir@ -includedir = @includedir@ -infodir = @infodir@ -install_sh = @install_sh@ -libdir = @libdir@ -libexecdir = @libexecdir@ -localedir = @localedir@ -localstatedir = @localstatedir@ -mandir = @mandir@ -mkdir_p = @mkdir_p@ -oldincludedir = @oldincludedir@ -pdfdir = @pdfdir@ -pkgpyexecdir = @pkgpyexecdir@ -pkgpythondir = @pkgpythondir@ -prefix = @prefix@ -program_transform_name = @program_transform_name@ -psdir = @psdir@ -pyexecdir = @pyexecdir@ -pythondir = @pythondir@ -sbindir = @sbindir@ -sharedstatedir = @sharedstatedir@ -srcdir = @srcdir@ -sysconfdir = @sysconfdir@ -target_alias = @target_alias@ -top_build_prefix = @top_build_prefix@ -top_builddir = @top_builddir@ -top_srcdir = @top_srcdir@ -CLEANFILES = Regress-1.0.gir Regress-1.0.typelib \ - GIMarshallingTests-1.0.gir GIMarshallingTests-1.0.typelib \ - gschemas.compiled -check_LTLIBRARIES = libgimarshallingtests.la $(am__append_1) \ - testhelper.la -test_typelibs = GIMarshallingTests-1.0.typelib $(am__append_2) -nodist_libgimarshallingtests_la_SOURCES = $(GI_DATADIR)/tests/gimarshallingtests.c $(GI_DATADIR)/tests/gimarshallingtests.h -libgimarshallingtests_la_CFLAGS = $(GLIB_CFLAGS) -libgimarshallingtests_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) -@ENABLE_CAIRO_TRUE@nodist_libregress_la_SOURCES = $(GI_DATADIR)/tests/regress.c $(GI_DATADIR)/tests/regress.h -@ENABLE_CAIRO_TRUE@libregress_la_CFLAGS = $(GIO_CFLAGS) $(CAIRO_CFLAGS) -@ENABLE_CAIRO_TRUE@libregress_la_LDFLAGS = -module -avoid-version $(GIO_LIBS) $(CAIRO_LIBS) -testhelper_la_CFLAGS = -I$(top_srcdir)/gi/_gobject -I$(top_srcdir)/gi/_glib $(PYTHON_INCLUDES) $(GLIB_CFLAGS) -testhelper_la_LDFLAGS = -module -avoid-version -testhelper_la_LIBADD = $(GLIB_LIBS) -testhelper_la_SOURCES = \ - testhelpermodule.c \ - test-floating.c \ - test-thread.c \ - test-unknown.c - -EXTRA_DIST = \ - compathelper.py \ - runtests.py \ - runtests-windows.py \ - testmodule.py \ - test-floating.h \ - test-thread.h \ - test-unknown.h \ - te_ST@nouppera \ - org.gnome.test.gschema.xml \ - test_gio.py \ - test_glib.py \ - test_gobject.py \ - test_gtype.py \ - test_interface.py \ - test_internal_api.py \ - test_iochannel.py \ - test_mainloop.py \ - test_object_marshaling.py \ - test_option.py \ - test_properties.py \ - test_signal.py \ - test_source.py \ - test_subprocess.py \ - test_thread.py \ - test_everything.py \ - test_gi.py \ - test_gdbus.py \ - test_overrides.py \ - test_overrides_glib.py \ - test_overrides_pango.py \ - test_overrides_gdk.py \ - test_overrides_gtk.py \ - test_atoms.py \ - test_generictreemodel.py \ - test_docstring.py \ - compat_test_pygtk.py \ - gi/__init__.py \ - gi/overrides/__init__.py \ - gi/overrides/Regress.py \ - $(NULL) - -DBUS_LAUNCH = $(shell which dbus-launch) -RUN_TESTS_ENV_VARS = \ - PYTHONPATH=$(top_builddir):$(top_builddir)/tests:$${PYTHONPATH:+:$$PYTHONPATH} \ - LD_LIBRARY_PATH=$(builddir)/.libs:$$LD_LIBRARY_PATH \ - GI_TYPELIB_PATH=$(builddir):$$GI_TYPELIB_PATH \ - XDG_DATA_DIRS=$$XDG_DATA_DIRS:/usr/share \ - MALLOC_PERTURB_=85 \ - MALLOC_CHECK_=3 \ - G_SLICE=debug-blocks \ - TESTS_BUILDDIR=$(builddir) - -all: all-am - -.SUFFIXES: -.SUFFIXES: .c .la .lo .o .obj .so -$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) - @for dep in $?; do \ - case '$(am__configure_deps)' in \ - *$$dep*) \ - ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ - && { if test -f $@; then exit 0; else break; fi; }; \ - exit 1;; \ - esac; \ - done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tests/Makefile'; \ - $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --foreign tests/Makefile -.PRECIOUS: Makefile -Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status - @case '$?' in \ - *config.status*) \ - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ - *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ - esac; - -$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh - -$(top_srcdir)/configure: $(am__configure_deps) - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh -$(ACLOCAL_M4): $(am__aclocal_m4_deps) - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh -$(am__aclocal_m4_deps): - -clean-checkLTLIBRARIES: - -test -z "$(check_LTLIBRARIES)" || rm -f $(check_LTLIBRARIES) - @list='$(check_LTLIBRARIES)'; \ - locs=`for p in $$list; do echo $$p; done | \ - sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ - sort -u`; \ - test -z "$$locs" || { \ - echo rm -f $${locs}; \ - rm -f $${locs}; \ - } - -@ENABLE_CAIRO_FALSE@libregress.la: $(libregress_la_OBJECTS) $(libregress_la_DEPENDENCIES) $(EXTRA_libregress_la_DEPENDENCIES) -@ENABLE_CAIRO_FALSE@ $(AM_V_CCLD)$(libregress_la_LINK) $(am_libregress_la_rpath) $(libregress_la_OBJECTS) $(libregress_la_LIBADD) $(LIBS) - -mostlyclean-compile: - -rm -f *.$(OBJEXT) - -distclean-compile: - -rm -f *.tab.c - -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgimarshallingtests_la-gimarshallingtests.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libregress_la-regress.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testhelper_la-test-floating.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testhelper_la-test-thread.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testhelper_la-test-unknown.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testhelper_la-testhelpermodule.Plo@am__quote@ - -.c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< - -.c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` - -.c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< - -libgimarshallingtests_la-gimarshallingtests.lo: $(GI_DATADIR)/tests/gimarshallingtests.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgimarshallingtests_la_CFLAGS) $(CFLAGS) -MT libgimarshallingtests_la-gimarshallingtests.lo -MD -MP -MF $(DEPDIR)/libgimarshallingtests_la-gimarshallingtests.Tpo -c -o libgimarshallingtests_la-gimarshallingtests.lo `test -f '$(GI_DATADIR)/tests/gimarshallingtests.c' || echo '$(srcdir)/'`$(GI_DATADIR)/tests/gimarshallingtests.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgimarshallingtests_la-gimarshallingtests.Tpo $(DEPDIR)/libgimarshallingtests_la-gimarshallingtests.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(GI_DATADIR)/tests/gimarshallingtests.c' object='libgimarshallingtests_la-gimarshallingtests.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgimarshallingtests_la_CFLAGS) $(CFLAGS) -c -o libgimarshallingtests_la-gimarshallingtests.lo `test -f '$(GI_DATADIR)/tests/gimarshallingtests.c' || echo '$(srcdir)/'`$(GI_DATADIR)/tests/gimarshallingtests.c - -libregress_la-regress.lo: $(GI_DATADIR)/tests/regress.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libregress_la_CFLAGS) $(CFLAGS) -MT libregress_la-regress.lo -MD -MP -MF $(DEPDIR)/libregress_la-regress.Tpo -c -o libregress_la-regress.lo `test -f '$(GI_DATADIR)/tests/regress.c' || echo '$(srcdir)/'`$(GI_DATADIR)/tests/regress.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libregress_la-regress.Tpo $(DEPDIR)/libregress_la-regress.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(GI_DATADIR)/tests/regress.c' object='libregress_la-regress.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libregress_la_CFLAGS) $(CFLAGS) -c -o libregress_la-regress.lo `test -f '$(GI_DATADIR)/tests/regress.c' || echo '$(srcdir)/'`$(GI_DATADIR)/tests/regress.c - -testhelper_la-testhelpermodule.lo: testhelpermodule.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -MT testhelper_la-testhelpermodule.lo -MD -MP -MF $(DEPDIR)/testhelper_la-testhelpermodule.Tpo -c -o testhelper_la-testhelpermodule.lo `test -f 'testhelpermodule.c' || echo '$(srcdir)/'`testhelpermodule.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/testhelper_la-testhelpermodule.Tpo $(DEPDIR)/testhelper_la-testhelpermodule.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='testhelpermodule.c' object='testhelper_la-testhelpermodule.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -c -o testhelper_la-testhelpermodule.lo `test -f 'testhelpermodule.c' || echo '$(srcdir)/'`testhelpermodule.c - -testhelper_la-test-floating.lo: test-floating.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -MT testhelper_la-test-floating.lo -MD -MP -MF $(DEPDIR)/testhelper_la-test-floating.Tpo -c -o testhelper_la-test-floating.lo `test -f 'test-floating.c' || echo '$(srcdir)/'`test-floating.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/testhelper_la-test-floating.Tpo $(DEPDIR)/testhelper_la-test-floating.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-floating.c' object='testhelper_la-test-floating.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -c -o testhelper_la-test-floating.lo `test -f 'test-floating.c' || echo '$(srcdir)/'`test-floating.c - -testhelper_la-test-thread.lo: test-thread.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -MT testhelper_la-test-thread.lo -MD -MP -MF $(DEPDIR)/testhelper_la-test-thread.Tpo -c -o testhelper_la-test-thread.lo `test -f 'test-thread.c' || echo '$(srcdir)/'`test-thread.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/testhelper_la-test-thread.Tpo $(DEPDIR)/testhelper_la-test-thread.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-thread.c' object='testhelper_la-test-thread.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -c -o testhelper_la-test-thread.lo `test -f 'test-thread.c' || echo '$(srcdir)/'`test-thread.c - -testhelper_la-test-unknown.lo: test-unknown.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -MT testhelper_la-test-unknown.lo -MD -MP -MF $(DEPDIR)/testhelper_la-test-unknown.Tpo -c -o testhelper_la-test-unknown.lo `test -f 'test-unknown.c' || echo '$(srcdir)/'`test-unknown.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/testhelper_la-test-unknown.Tpo $(DEPDIR)/testhelper_la-test-unknown.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-unknown.c' object='testhelper_la-test-unknown.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(testhelper_la_CFLAGS) $(CFLAGS) -c -o testhelper_la-test-unknown.lo `test -f 'test-unknown.c' || echo '$(srcdir)/'`test-unknown.c - -mostlyclean-libtool: - -rm -f *.lo - -clean-libtool: - -rm -rf .libs _libs - -ID: $(am__tagged_files) - $(am__define_uniq_tagged_files); mkid -fID $$unique -tags: tags-am -TAGS: tags - -tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) - set x; \ - here=`pwd`; \ - $(am__define_uniq_tagged_files); \ - shift; \ - if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ - test -n "$$unique" || unique=$$empty_fix; \ - if test $$# -gt 0; then \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - "$$@" $$unique; \ - else \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$unique; \ - fi; \ - fi -ctags: ctags-am - -CTAGS: ctags -ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) - $(am__define_uniq_tagged_files); \ - test -z "$(CTAGS_ARGS)$$unique" \ - || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ - $$unique - -GTAGS: - here=`$(am__cd) $(top_builddir) && pwd` \ - && $(am__cd) $(top_srcdir) \ - && gtags -i $(GTAGS_ARGS) "$$here" -cscopelist: cscopelist-am - -cscopelist-am: $(am__tagged_files) - list='$(am__tagged_files)'; \ - case "$(srcdir)" in \ - [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ - *) sdir=$(subdir)/$(srcdir) ;; \ - esac; \ - for i in $$list; do \ - if test -f "$$i"; then \ - echo "$(subdir)/$$i"; \ - else \ - echo "$$sdir/$$i"; \ - fi; \ - done >> $(top_builddir)/cscope.files - -distclean-tags: - -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags - -distdir: $(DISTFILES) - @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - list='$(DISTFILES)'; \ - dist_files=`for file in $$list; do echo $$file; done | \ - sed -e "s|^$$srcdirstrip/||;t" \ - -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ - case $$dist_files in \ - */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ - sort -u` ;; \ - esac; \ - for file in $$dist_files; do \ - if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ - if test -d $$d/$$file; then \ - dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ - if test -d "$(distdir)/$$file"; then \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ - cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ - else \ - test -f "$(distdir)/$$file" \ - || cp -p $$d/$$file "$(distdir)/$$file" \ - || exit 1; \ - fi; \ - done -check-am: all-am - $(MAKE) $(AM_MAKEFLAGS) $(check_LTLIBRARIES) - $(MAKE) $(AM_MAKEFLAGS) check-local -check: check-am -all-am: Makefile -installdirs: -install: install-am -install-exec: install-exec-am -install-data: install-data-am -uninstall: uninstall-am - -install-am: all-am - @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am - -installcheck: installcheck-am -install-strip: - if test -z '$(STRIP)'; then \ - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - install; \ - else \ - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ - fi -mostlyclean-generic: - -clean-generic: - -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) - -distclean-generic: - -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) - -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) - -maintainer-clean-generic: - @echo "This command is intended for maintainers to use" - @echo "it deletes files that may require special tools to rebuild." -clean: clean-am - -clean-am: clean-checkLTLIBRARIES clean-generic clean-libtool \ - clean-local mostlyclean-am - -distclean: distclean-am - -rm -rf ./$(DEPDIR) - -rm -f Makefile -distclean-am: clean-am distclean-compile distclean-generic \ - distclean-tags - -dvi: dvi-am - -dvi-am: - -html: html-am - -html-am: - -info: info-am - -info-am: - -install-data-am: - -install-dvi: install-dvi-am - -install-dvi-am: - -install-exec-am: - -install-html: install-html-am - -install-html-am: - -install-info: install-info-am - -install-info-am: - -install-man: - -install-pdf: install-pdf-am - -install-pdf-am: - -install-ps: install-ps-am - -install-ps-am: - -installcheck-am: - -maintainer-clean: maintainer-clean-am - -rm -rf ./$(DEPDIR) - -rm -f Makefile -maintainer-clean-am: distclean-am maintainer-clean-generic - -mostlyclean: mostlyclean-am - -mostlyclean-am: mostlyclean-compile mostlyclean-generic \ - mostlyclean-libtool - -pdf: pdf-am - -pdf-am: - -ps: ps-am - -ps-am: - -uninstall-am: - -.MAKE: check-am install-am install-strip - -.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-local clean \ - clean-checkLTLIBRARIES clean-generic clean-libtool clean-local \ - cscopelist-am ctags ctags-am distclean distclean-compile \ - distclean-generic distclean-libtool distclean-tags distdir dvi \ - dvi-am html html-am info info-am install install-am \ - install-data install-data-am install-dvi install-dvi-am \ - install-exec install-exec-am install-html install-html-am \ - install-info install-info-am install-man install-pdf \ - install-pdf-am install-ps install-ps-am install-strip \ - installcheck installcheck-am installdirs maintainer-clean \ - maintainer-clean-generic mostlyclean mostlyclean-compile \ - mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ - tags tags-am uninstall uninstall-am - - -# This is a hack to make sure a shared library is built -libgimarshallingtests.la: $(libgimarshallingtests_la_OBJECTS) $(libgimarshallingtests_la_DEPENDENCIES) - $(AM_V_GEN) $(LINK) -rpath $(pkgpyexecdir) $(libgimarshallingtests_la_LDFLAGS) $(libgimarshallingtests_la_OBJECTS) $(libgimarhallingtests_la_LIBADD) $(LIBS) - -GIMarshallingTests-1.0.gir: libgimarshallingtests.la Makefile - $(AM_V_GEN) g-ir-scanner --include=Gio-2.0 \ - --namespace=GIMarshallingTests --nsversion=1.0 --symbol-prefix=gi_marshalling_tests \ - --warn-all --warn-error \ - --library=libgimarshallingtests.la \ - --libtool="$(top_builddir)/libtool" \ - --output $@ \ - $(nodist_libgimarshallingtests_la_SOURCES) -GIMarshallingTests-1.0.typelib: GIMarshallingTests-1.0.gir Makefile - $(AM_V_GEN) g-ir-compiler $< -o $@ - -@ENABLE_CAIRO_TRUE@libregress.la: $(libregress_la_OBJECTS) $(libregress_la_DEPENDENCIES) -@ENABLE_CAIRO_TRUE@ $(AM_V_GEN) $(LINK) -rpath $(pkgpyexecdir) $(libregress_la_LDFLAGS) $(libregress_la_OBJECTS) $(libregress_la_LIBADD) $(LIBS) - -# g-i doesn't ship these as shared libraries anymore; we build them here -@ENABLE_CAIRO_TRUE@Regress-1.0.gir: libregress.la Makefile -@ENABLE_CAIRO_TRUE@ $(AM_V_GEN) g-ir-scanner --include=cairo-1.0 --include=Gio-2.0 \ -@ENABLE_CAIRO_TRUE@ --namespace=Regress --nsversion=1.0 \ -@ENABLE_CAIRO_TRUE@ --warn-all --warn-error \ -@ENABLE_CAIRO_TRUE@ --library=libregress.la \ -@ENABLE_CAIRO_TRUE@ --libtool="$(top_builddir)/libtool" \ -@ENABLE_CAIRO_TRUE@ --output $@ \ -@ENABLE_CAIRO_TRUE@ $(nodist_libregress_la_SOURCES) -@ENABLE_CAIRO_TRUE@Regress-1.0.typelib: Regress-1.0.gir Makefile -@ENABLE_CAIRO_TRUE@ $(AM_V_GEN) g-ir-compiler $< -o $@ - -gschemas.compiled: org.gnome.test.gschema.xml - glib-compile-schemas --targetdir=. --schema-file=$< - -# This is a hack to make sure a shared library is built -testhelper.la: $(testhelper_la_OBJECTS) $(testhelper_la_DEPENDENCIES) - $(AM_V_GEN) $(LINK) -rpath $(pkgpyexecdir) $(testhelper_la_LDFLAGS) $(testhelper_la_OBJECTS) $(testhelper_la_LIBADD) $(LIBS) - -.la.so: - test -L $@ || $(LN_S) .libs/$@ $@ - -all: $(check_LTLIBRARIES:.la=.so) - -clean-local: - rm -f $(check_LTLIBRARIES:.la=.so) file.txt~ - -# pygtkcompat tests need to be run in a separate process as they -# clobber global name space -check-local: $(check_LTLIBRARIES:.la=.so) $(test_typelibs) gschemas.compiled - @echo " CHECK Pyflakes" - @if type pyflakes >/dev/null 2>&1; then pyflakes $(top_srcdir); else echo "skipped, pyflakes not installed"; fi - @if test -z "$$SKIP_PEP8"; then \ - echo " CHECK PEP8"; \ - if type pep8 >/dev/null 2>&1; then pep8 --ignore=E501,E123,E124 --repeat --show-source $(top_srcdir); else echo "skipped, pep8 not installed"; fi; \ - fi - export `$(DBUS_LAUNCH)` && \ - $(RUN_TESTS_ENV_VARS) $(EXEC_NAME) $(PYTHON) -Wd $(srcdir)/runtests.py; rc=$$?; \ - [ "$$rc" -ne 0 ] || [ -n "$$TEST_NAMES" ] || { TEST_NAMES=compat_test_pygtk $(RUN_TESTS_ENV_VARS) $(EXEC_NAME) $(PYTHON) -Wd -Werror::PendingDeprecationWarning -Werror::DeprecationWarning -Werror::RuntimeWarning $(srcdir)/runtests.py; rc=$$?; }; \ - kill $$DBUS_SESSION_BUS_PID; \ - exit $$rc - -check.gdb: - EXEC_NAME="gdb --args" $(MAKE) check - -check.nemiver: - EXEC_NAME="nemiver" $(MAKE) check - -check.valgrind: - EXEC_NAME="valgrind --leak-check=full --show-possibly-lost=no --suppressions=python.supp" G_DEBUG=gc-friendly $(MAKE) check - -# Tell versions [3.59,3.63) of GNU make to not export all variables. -# Otherwise a system limit (for SysV at least) may be exceeded. -.NOEXPORT: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..a7b7ff2 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,137 @@ +import os +import sys +import signal +import subprocess +import atexit +import warnings + + +def set_dll_search_path(): + # Python 3.8 no longer searches for DLLs in PATH, so we have to add + # everything in PATH manually. Note that unlike PATH add_dll_directory + # has no defined order, so if there are two cairo DLLs in PATH we + # might get a random one. + if os.name != "nt" or not hasattr(os, "add_dll_directory"): + return + for p in os.environ.get("PATH", "").split(os.pathsep): + try: + os.add_dll_directory(p) + except OSError: + pass + + +def init_test_environ(): + + set_dll_search_path() + + def dbus_launch_session(): + if os.name == "nt" or sys.platform == "darwin": + return (-1, "") + + try: + out = subprocess.check_output([ + "dbus-daemon", "--session", "--fork", "--print-address=1", + "--print-pid=1"]) + except (subprocess.CalledProcessError, OSError): + return (-1, "") + else: + out = out.decode("utf-8") + addr, pid = out.splitlines() + return int(pid), addr + + pid, addr = dbus_launch_session() + if pid >= 0: + os.environ["DBUS_SESSION_BUS_ADDRESS"] = addr + atexit.register(os.kill, pid, signal.SIGKILL) + else: + os.environ["DBUS_SESSION_BUS_ADDRESS"] = "." + + tests_builddir = os.path.abspath(os.environ.get('TESTS_BUILDDIR', os.path.dirname(__file__))) + builddir = os.path.dirname(tests_builddir) + tests_srcdir = os.path.abspath(os.path.dirname(__file__)) + srcdir = os.path.dirname(tests_srcdir) + + sys.path.insert(0, os.path.join(builddir, 'gi')) + sys.path.insert(0, tests_srcdir) + sys.path.insert(0, srcdir) + sys.path.insert(0, tests_builddir) + sys.path.insert(0, builddir) + + # force untranslated messages, as we check for them in some tests + os.environ['LC_MESSAGES'] = 'C' + os.environ['G_DEBUG'] = 'fatal-warnings fatal-criticals' + if sys.platform == "darwin" or os.name == "nt": + # gtk 3.22 has warnings and ciriticals on OS X, ignore for now. + # On Windows glib will create an error dialog which will block tests + # so it's never a good idea there to make things fatal. + os.environ['G_DEBUG'] = '' + + # make Gio able to find our gschemas.compiled in tests/. This needs to be set + # before importing Gio. Support a separate build tree, so look in build dir + # first. + os.environ['GSETTINGS_BACKEND'] = 'memory' + os.environ['GSETTINGS_SCHEMA_DIR'] = tests_builddir + os.environ['G_FILENAME_ENCODING'] = 'UTF-8' + + # Avoid accessibility dbus warnings + os.environ['NO_AT_BRIDGE'] = '1' + + # A workaround for https://gitlab.gnome.org/GNOME/glib/-/issues/2251 + # The gtk4 a11y stack calls get_dbus_object_path() on the default app + os.environ['GTK_A11Y'] = 'none' + + # Force the default theme so broken themes don't affect the tests + os.environ['GTK_THEME'] = 'Adwaita' + + import gi + gi.require_version("GIRepository", "2.0") + from gi.repository import GIRepository + repo = GIRepository.Repository.get_default() + repo.prepend_library_path(os.path.join(tests_builddir)) + repo.prepend_search_path(tests_builddir) + + def try_require_version(namespace, version): + try: + gi.require_version(namespace, version) + except ValueError: + # prevent tests from running with the wrong version + sys.modules["gi.repository." + namespace] = None + + # Optional + try_require_version("Gtk", os.environ.get("TEST_GTK_VERSION", "3.0")) + try_require_version("Gdk", os.environ.get("TEST_GTK_VERSION", "3.0")) + try_require_version("GdkPixbuf", "2.0") + try_require_version("Pango", "1.0") + try_require_version("PangoCairo", "1.0") + try_require_version("Atk", "1.0") + + # Required + gi.require_versions({ + "GIMarshallingTests": "1.0", + "Regress": "1.0", + "GLib": "2.0", + "Gio": "2.0", + "GObject": "2.0", + }) + + # It's disabled for stable releases by default, this makes sure it's + # always on for the tests. + warnings.simplefilter('default', gi.PyGIDeprecationWarning) + + # Otherwise we crash on the first gtk use when e.g. DISPLAY isn't set + try: + from gi.repository import Gtk + except ImportError: + pass + else: + if Gtk._version == "4.0": + res = Gtk.init_check() + else: + res = Gtk.init_check([])[0] + if not res: + raise RuntimeError("Gtk available, but Gtk.init_check() failed") + + +init_test_environ() + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/tests/compat_test_pygtk.py b/tests/compat_test_pygtk.py deleted file mode 100644 index 10be6a3..0000000 --- a/tests/compat_test_pygtk.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- Mode: Python; py-indent-offset: 4 -*- -# vim: tabstop=4 shiftwidth=4 expandtab - -import unittest - -from gi.repository import GLib - -try: - from gi.repository import Pango - from gi.repository import Atk - from gi.repository import Gdk - from gi.repository import Gtk - (Atk, Gtk, Pango) # pyflakes - - import pygtkcompat - - pygtkcompat.enable() - pygtkcompat.enable_gtk(version='3.0') - - import atk - import pango - import pangocairo - import gtk - import gtk.gdk -except ImportError: - Gtk = None - - -@unittest.skipUnless(Gtk, 'Gtk not available') -class TestATKCompat(unittest.TestCase): - def test_object(self): - self.assertTrue(hasattr(atk, 'Object')) - - -@unittest.skipUnless(Gtk, 'Gtk not available') -class TestPangoCompat(unittest.TestCase): - def test_layout(self): - self.assertTrue(hasattr(pango, 'Layout')) - - -@unittest.skipUnless(Gtk, 'Gtk not available') -class TestPangoCairoCompat(unittest.TestCase): - def test_error_underline_path(self): - self.assertTrue(hasattr(pangocairo, 'error_underline_path')) - - -@unittest.skipUnless(Gtk, 'Gtk not available') -class TestGTKCompat(unittest.TestCase): - def test_buttons(self): - self.assertEqual(Gdk._2BUTTON_PRESS, 5) - self.assertEqual(Gdk.BUTTON_PRESS, 4) - - def test_enums(self): - self.assertEqual(gtk.WINDOW_TOPLEVEL, Gtk.WindowType.TOPLEVEL) - self.assertEqual(gtk.PACK_START, Gtk.PackType.START) - - def test_flags(self): - self.assertEqual(gtk.EXPAND, Gtk.AttachOptions.EXPAND) - self.assertEqual(gtk.gdk.SHIFT_MASK, Gdk.ModifierType.SHIFT_MASK) - - def test_keysyms(self): - import gtk.keysyms - self.assertEqual(gtk.keysyms.Escape, Gdk.KEY_Escape) - self.assertTrue(gtk.keysyms._0, Gdk.KEY_0) - - def test_style(self): - widget = gtk.Button() - self.assertTrue(isinstance(widget.style.base[gtk.STATE_NORMAL], - gtk.gdk.Color)) - - def test_alignment(self): - a = gtk.Alignment() - self.assertEqual(a.props.xalign, 0.0) - self.assertEqual(a.props.yalign, 0.0) - self.assertEqual(a.props.xscale, 0.0) - self.assertEqual(a.props.yscale, 0.0) - - def test_box(self): - box = gtk.Box() - child = gtk.Button() - - box.pack_start(child) - expand, fill, padding, pack_type = box.query_child_packing(child) - self.assertTrue(expand) - self.assertTrue(fill) - self.assertEqual(padding, 0) - self.assertEqual(pack_type, gtk.PACK_START) - - child = gtk.Button() - box.pack_end(child) - expand, fill, padding, pack_type = box.query_child_packing(child) - self.assertTrue(expand) - self.assertTrue(fill) - self.assertEqual(padding, 0) - self.assertEqual(pack_type, gtk.PACK_END) - - def test_combobox_entry(self): - liststore = gtk.ListStore(int, str) - liststore.append((1, 'One')) - liststore.append((2, 'Two')) - liststore.append((3, 'Three')) - # might cause a Pango warning, do not break on this - old_mask = GLib.log_set_always_fatal( - GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR) - try: - combo = gtk.ComboBoxEntry(model=liststore) - finally: - GLib.log_set_always_fatal(old_mask) - combo.set_text_column(1) - combo.set_active(0) - self.assertEqual(combo.get_text_column(), 1) - self.assertEqual(combo.get_child().get_text(), 'One') - combo = gtk.combo_box_entry_new() - combo.set_model(liststore) - combo.set_text_column(1) - combo.set_active(0) - self.assertEqual(combo.get_text_column(), 1) - self.assertEqual(combo.get_child().get_text(), 'One') - combo = gtk.combo_box_entry_new_with_model(liststore) - combo.set_text_column(1) - combo.set_active(0) - self.assertEqual(combo.get_text_column(), 1) - self.assertEqual(combo.get_child().get_text(), 'One') - - def test_size_request(self): - box = gtk.Box() - self.assertEqual(box.size_request(), [0, 0]) - - def test_pixbuf(self): - gtk.gdk.Pixbuf() - - def test_pixbuf_loader(self): - loader = gtk.gdk.PixbufLoader('png') - loader.close() - - def test_pixbuf_formats(self): - formats = gtk.gdk.pixbuf_get_formats() - self.assertEqual(type(formats[0]), dict) - self.assertTrue('name' in formats[0]) - self.assertTrue('description' in formats[0]) - self.assertTrue('mime_types' in formats[0]) - self.assertEqual(type(formats[0]['extensions']), list) - - def test_gdk_window(self): - w = gtk.Window() - w.realize() - self.assertEqual(w.get_window().get_origin(), (0, 0)) diff --git a/tests/compathelper.py b/tests/compathelper.py deleted file mode 100644 index 668e60a..0000000 --- a/tests/compathelper.py +++ /dev/null @@ -1,69 +0,0 @@ -import sys - -if sys.version_info >= (3, 0): - ''' - for tests that need to test long values in python 2 - - python 3 does not differentiate between long and int - and does not supply a long keyword - - instead of testing longs by using values such as 10L - test writters should do this: - - from compathelper import _long - _long(10) - ''' - _long = int - - ''' - for tests that need to test string values in python 2 - - python 3 does differentiate between str and bytes - and does not supply a basestring keyword - - any tests that use basestring should do this: - - from compathelper import _basestring - isinstance(_basestring, "hello") - ''' - _basestring = str - - ''' - for tests that need to write to intefaces that take bytes in - python 3 - - python 3 has a seperate bytes type for low level functions like os.write - - python 2 treats these as strings - - any tests that need to write a string of bytes should do something like - this: - - from compathelper import _bytes - os.write(_bytes("hello")) - ''' - - _bytes = lambda s: s.encode() - - ''' - for tests that need to write to intefaces that take unicode in - python 2 - - python 3 strings are unicode encoded as UTF-8 so the unicode object - doesn't exist - - python 2 differs between a string an unicode string and you must specify - an encoding. This macro will specify UTF-8 in python 2 - - any tests that need to use unicode should do this - - from compathelper import _unicode - unicode_string = _unicode('this is a unicode string') - ''' - - _unicode = lambda s: str(s) -else: - _long = long - _basestring = basestring - _bytes = str - _unicode = lambda s: unicode(s, 'UTF-8') diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..254a721 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,26 @@ +import sys + +import pytest + + +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_call(item): + """A pytest hook which takes over sys.excepthook and raises any uncaught + exception (with PyGObject this happesn often when we get called from C, + like any signal handler, vfuncs tc) + """ + + exceptions = [] + + def on_hook(type_, value, tback): + exceptions.append((type_, value, tback)) + + orig_excepthook = sys.excepthook + sys.excepthook = on_hook + try: + yield + finally: + sys.excepthook = orig_excepthook + if exceptions: + tp, value, tb = exceptions[0] + raise tp(value).with_traceback(tb) diff --git a/tests/gi/__init__.py b/tests/gi/__init__.py deleted file mode 100644 index 3ad9513..0000000 --- a/tests/gi/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) diff --git a/tests/gimarshallingtestsextra.c b/tests/gimarshallingtestsextra.c new file mode 100644 index 0000000..d4d6189 --- /dev/null +++ b/tests/gimarshallingtestsextra.c @@ -0,0 +1,177 @@ +/* gimarshallingtestsextra.c + * + * Copyright (C) 2016 Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "gimarshallingtestsextra.h" +#include <string.h> + +void +gi_marshalling_tests_compare_two_gerrors_in_gvalue (GValue *v, GValue *v1) +{ + GError *error, * error1; + + g_assert_cmpstr (g_type_name (G_VALUE_TYPE (v)), ==, + g_type_name (G_TYPE_ERROR)); + g_assert_cmpstr (g_type_name (G_VALUE_TYPE (v1)), ==, + g_type_name (G_TYPE_ERROR)); + + error = (GError*) g_value_get_boxed (v); + error1 = (GError*) g_value_get_boxed (v1); + + g_assert_cmpint (error->domain, ==, error1->domain); + g_assert_cmpint (error->code, ==, error1->code); + g_assert_cmpstr (error->message, ==, error1->message); +} + +/** + * gi_marshalling_tests_ghashtable_enum_none_in: + * @hash_table: (element-type gint GIMarshallingTestsExtraEnum) (transfer none): + */ +void +gi_marshalling_tests_ghashtable_enum_none_in (GHashTable *hash_table) +{ + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (1))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (2))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2); + g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (3))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3); +} + +/** + * gi_marshalling_tests_ghashtable_enum_none_return: + * + * Returns: (element-type gint GIMarshallingTestsExtraEnum) (transfer none): + */ +GHashTable * +gi_marshalling_tests_ghashtable_enum_none_return (void) +{ + static GHashTable *hash_table = NULL; + + if (hash_table == NULL) + { + hash_table = g_hash_table_new (NULL, NULL); + g_hash_table_insert (hash_table, GINT_TO_POINTER (1), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (2), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2)); + g_hash_table_insert (hash_table, GINT_TO_POINTER (3), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3)); + } + + return hash_table; +} + +/** + * gi_marshalling_tests_filename_copy: + * @path_in: (type filename) (nullable) + * + * Returns: (type filename) (nullable) + */ +gchar * +gi_marshalling_tests_filename_copy (gchar *path_in) +{ + return g_strdup (path_in); +} + +/** + * gi_marshalling_tests_filename_to_glib_repr: + * @path_in: (type filename) (nullable) + * + * Returns: (array length=len) (element-type guint8) + */ +gchar * +gi_marshalling_tests_filename_to_glib_repr (gchar *path_in, gsize *len) +{ + *len = strlen(path_in); + return g_strdup (path_in); +} + +/** + * gi_marshalling_tests_filename_exists: + * @path: (type filename) + */ +gboolean +gi_marshalling_tests_filename_exists (gchar *path) +{ + return g_file_test (path, G_FILE_TEST_EXISTS); +} + + +/** + * gi_marshalling_tests_enum_array_return_type: + * @n_members: (out): The number of members + * + * Returns: (array length=n_members) (transfer full): An array of enum values + */ +GIMarshallingTestsExtraEnum * +gi_marshalling_tests_enum_array_return_type (gsize *n_members) +{ + GIMarshallingTestsExtraEnum *res = g_new0(GIMarshallingTestsExtraEnum, 3); + + *n_members = 3; + + res[0] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1; + res[1] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2; + res[2] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3; + + return res; +} + +GType +gi_marshalling_tests_extra_flags_get_type (void) +{ + static GType type = 0; + if (G_UNLIKELY (type == 0)) + { + static const GFlagsValue values[] = { + {GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1, + "GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1", "value1"}, + {GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2, + "GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2", "value2"}, + {0, NULL, NULL} + }; + type = g_flags_register_static ( + g_intern_static_string ("GIMarshallingTestsExtraFlags"), values); + } + + return type; +} + +/** + * gi_marshalling_tests_extra_flags_large_in: + */ +void +gi_marshalling_tests_extra_flags_large_in (GIMarshallingTestsExtraFlags value) +{ + g_assert_cmpint (value, ==, GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2); +} + + +/** + * gi_marshalling_tests_extra_utf8_full_return_invalid: + */ +gchar * +gi_marshalling_tests_extra_utf8_full_return_invalid (void) +{ + return g_strdup ("invalid utf8 \xff\xfe"); +} + + +/** + * gi_marshalling_tests_extra_utf8_full_out_invalid: + * @utf8: (out) (transfer full): + */ +void +gi_marshalling_tests_extra_utf8_full_out_invalid (gchar **utf8) +{ + *utf8 = g_strdup ("invalid utf8 \xff\xfe"); +} diff --git a/tests/gimarshallingtestsextra.h b/tests/gimarshallingtestsextra.h new file mode 100644 index 0000000..68ce1d1 --- /dev/null +++ b/tests/gimarshallingtestsextra.h @@ -0,0 +1,69 @@ +/* gimarshallingtestsextra.h + * + * Copyright (C) 2016 Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef EXTRA_TESTS +#define EXTRA_TESTS + +#include <glib-object.h> +#include <gitestmacros.h> + +typedef enum +{ + GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1, + GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2, + GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3 = 42 +} GIMarshallingTestsExtraEnum; + + +typedef enum +{ + GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1 = 0, + GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2 = (gint)(1 << 31), +} GIMarshallingTestsExtraFlags; + + +_GI_TEST_EXTERN +GType gi_marshalling_tests_extra_flags_get_type (void) G_GNUC_CONST; +#define GI_MARSHALLING_TESTS_TYPE_EXTRA_FLAGS (gi_marshalling_tests_extra_flags_get_type ()) + +_GI_TEST_EXTERN +void gi_marshalling_tests_compare_two_gerrors_in_gvalue (GValue *v, GValue *v1); +_GI_TEST_EXTERN +void gi_marshalling_tests_ghashtable_enum_none_in (GHashTable *hash_table); +_GI_TEST_EXTERN +GHashTable * gi_marshalling_tests_ghashtable_enum_none_return (void); + +_GI_TEST_EXTERN +gchar * gi_marshalling_tests_filename_copy (gchar *path_in); +_GI_TEST_EXTERN +gboolean gi_marshalling_tests_filename_exists (gchar *path); +_GI_TEST_EXTERN +gchar * gi_marshalling_tests_filename_to_glib_repr (gchar *path_in, gsize *len); + +_GI_TEST_EXTERN +GIMarshallingTestsExtraEnum * gi_marshalling_tests_enum_array_return_type (gsize *n_members); + +_GI_TEST_EXTERN +void gi_marshalling_tests_extra_flags_large_in (GIMarshallingTestsExtraFlags value); + +_GI_TEST_EXTERN +gchar *gi_marshalling_tests_extra_utf8_full_return_invalid (void); +_GI_TEST_EXTERN +void gi_marshalling_tests_extra_utf8_full_out_invalid (gchar **utf8); + +#endif /* EXTRA_TESTS */ diff --git a/tests/helper.py b/tests/helper.py new file mode 100644 index 0000000..bed51e8 --- /dev/null +++ b/tests/helper.py @@ -0,0 +1,131 @@ +import contextlib +import unittest +import inspect +import warnings +import functools +import sys +from collections import namedtuple +from io import StringIO + +import gi +from gi import PyGIDeprecationWarning +from gi.repository import GLib + + +ExceptionInfo = namedtuple("ExceptionInfo", ["type", "value", "traceback"]) +"""The type used for storing exceptions used by capture_exceptions()""" + + +@contextlib.contextmanager +def capture_exceptions(): + """Installs a temporary sys.excepthook which records all exceptions + instead of printing them. + """ + + exceptions = [] + + def custom_excepthook(*args): + exceptions.append(ExceptionInfo(*args)) + + old_hook = sys.excepthook + sys.excepthook = custom_excepthook + try: + yield exceptions + finally: + sys.excepthook = old_hook + + +def ignore_gi_deprecation_warnings(func_or_class): + """A unittest class and function decorator which makes them ignore + PyGIDeprecationWarning. + """ + + if inspect.isclass(func_or_class): + assert issubclass(func_or_class, unittest.TestCase) + cls = func_or_class + for name, value in cls.__dict__.items(): + if callable(value) and name.startswith("test_"): + new_value = ignore_gi_deprecation_warnings(value) + setattr(cls, name, new_value) + return cls + else: + func = func_or_class + + @functools.wraps(func) + def wrapper(*args, **kwargs): + with capture_gi_deprecation_warnings(): + return func(*args, **kwargs) + + return wrapper + + +@contextlib.contextmanager +def capture_gi_deprecation_warnings(): + """Temporarily suppress PyGIDeprecationWarning output and record them""" + + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always', category=PyGIDeprecationWarning) + yield warn + + +@contextlib.contextmanager +def capture_glib_warnings(allow_warnings=False, allow_criticals=False): + """Temporarily suppress glib warning output and record them. + + The test suite is run with G_DEBUG="fatal-warnings fatal-criticals" + by default. Setting allow_warnings and allow_criticals will temporarily + allow warnings or criticals without terminating the test run. + """ + + old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags(0)) + + new_mask = old_mask + if allow_warnings: + new_mask &= ~GLib.LogLevelFlags.LEVEL_WARNING + if allow_criticals: + new_mask &= ~GLib.LogLevelFlags.LEVEL_CRITICAL + + GLib.log_set_always_fatal(GLib.LogLevelFlags(new_mask)) + + GLibWarning = gi._gi.Warning + try: + with warnings.catch_warnings(record=True) as warn: + warnings.filterwarnings('always', category=GLibWarning) + yield warn + finally: + GLib.log_set_always_fatal(old_mask) + + +@contextlib.contextmanager +def capture_glib_deprecation_warnings(): + """Temporarily suppress glib deprecation warning output and record them""" + + GLibWarning = gi._gi.Warning + with warnings.catch_warnings(record=True) as warn: + warnings.filterwarnings( + 'always', category=GLibWarning, + message=".+ is deprecated and shouldn't be used anymore\\. " + "It will be removed in a future version\\.") + yield warn + + +@contextlib.contextmanager +def capture_output(): + """ + with capture_output() as (stdout, stderr): + some_action() + print(stdout.getvalue(), stderr.getvalue()) + """ + + err = StringIO() + out = StringIO() + old_err = sys.stderr + old_out = sys.stdout + sys.stderr = err + sys.stdout = out + + try: + yield (out, err) + finally: + sys.stderr = old_err + sys.stdout = old_out diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..0a56ac5 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,133 @@ +gnome = import('gnome') + +host_system = host_machine.system() + +cc = meson.get_compiler('c') + +visibility_args = [] +if get_option('default_library') != 'static' + if host_system == 'windows' + visibility_args += ['-DDLL_EXPORT'] + if cc.get_id() == 'msvc' + visibility_args += ['-D_GI_EXTERN=__declspec(dllexport) extern'] + elif cc.has_argument('-fvisibility=hidden') + visibility_args += ['-D_GI_EXTERN=__attribute__((visibility("default"))) __declspec(dllexport) extern'] + visibility_args += ['-fvisibility=hidden'] + endif + elif cc.has_argument('-fvisibility=hidden') + visibility_args += ['-D_GI_EXTERN=__attribute__((visibility("default"))) extern'] + visibility_args += ['-fvisibility=hidden'] + endif +endif + +if gi_dep.type_name() == 'pkgconfig' + gi_datadir = gi_dep.get_variable(pkgconfig : 'gidatadir') + regress_sources = [join_paths(gi_datadir, 'tests', 'regress.c')] + regress_headers = [join_paths(gi_datadir, 'tests', 'regress.h')] + regress_incdir = include_directories(join_paths(gi_datadir, 'tests')) + marshalling_sources = [join_paths(gi_datadir, 'tests', 'gimarshallingtests.c')] + marshalling_headers = [join_paths(gi_datadir, 'tests', 'gimarshallingtests.h')] +else + gi_subproject = subproject('gobject-introspection') + regress_sources = gi_subproject.get_variable('test_regress_sources') + regress_headers = gi_subproject.get_variable('test_regress_headers') + regress_incdir = gi_subproject.get_variable('test_regress_incdirs') + marshalling_sources = gi_subproject.get_variable('test_marshalling_sources') + marshalling_headers = gi_subproject.get_variable('test_marshalling_headers') + gi_datadir = join_paths(meson.source_root(), 'subprojects', 'gobject-introspection', 'tests') +endif + +marshalling_sources += ['gimarshallingtestsextra.c'] + +marshalling_headers += ['gimarshallingtestsextra.h'] + +marshalling_lib = library( + 'gimarshallingtests', + sources : marshalling_sources, + dependencies : [glib_dep, gobject_dep, gio_dep, gmodule_dep], + include_directories : regress_incdir, + c_args: visibility_args, +) + +gnome.generate_gir( + marshalling_lib, + sources : marshalling_sources + marshalling_headers, + nsversion : '1.0', + namespace : 'GIMarshallingTests', + dependencies : [glib_dep, gobject_dep, gio_dep, gmodule_dep], + symbol_prefix : 'gi_marshalling_tests', + includes : ['Gio-2.0'], + build_by_default : true, + extra_args: ['--quiet'], +) + +regress_sources += ['regressextra.c'] + +regress_headers += ['regressextra.h'] + +regress_deps = [glib_dep, gobject_dep, gio_dep, gmodule_dep] +regress_c_args = [] + +if cairo_dep.found() + regress_deps += [cairo_dep, cairo_gobject_dep] +else + regress_c_args += ['-D_GI_DISABLE_CAIRO'] +endif + +regress_lib = library( + 'regress', + sources : regress_sources, + dependencies : regress_deps, + include_directories : regress_incdir, + c_args: regress_c_args + visibility_args, +) + +gnome.generate_gir( + regress_lib, + sources : regress_sources + regress_headers, + nsversion : '1.0', + namespace : 'Regress', + includes : ['Gio-2.0', 'cairo-1.0'], + build_by_default : true, + dependencies : regress_deps, + extra_args: regress_c_args + ['--quiet'], +) + +helper_sources = [ + 'testhelpermodule.c', + 'test-floating.c', + 'test-thread.c', + 'test-unknown.c'] + +helperext = python.extension_module('testhelper', helper_sources, + dependencies : [python_dep, glib_dep, gobject_dep], + c_args: pyext_c_args + main_c_args, + include_directories: include_directories(join_paths('..', 'gi')) +) + +schemas = gnome.compile_schemas(build_by_default: true) + +envdata = environment() +envdata.append('GI_TYPELIB_PATH', meson.current_build_dir()) +if gi_dep.type_name() == 'internal' + envdata.append('GI_TYPELIB_PATH', join_paths(meson.project_build_root(), 'subprojects', 'gobject-introspection', 'gir')) +endif + +if host_machine.system() == 'linux' + envdata.prepend('LD_LIBRARY_PATH', meson.current_build_dir()) +endif +if host_machine.system() == 'windows' + envdata.prepend('PATH', join_paths(get_option('prefix'), get_option('bindir'))) +endif + +python_paths = [join_paths(meson.current_build_dir(), '..')] +if pycairo_dep.found() and pycairo_dep.type_name() == 'internal' + python_paths += [join_paths(meson.project_build_root(), 'subprojects', 'pycairo')] +endif +envdata.append('PYTHONPATH', python_paths) +envdata.append('TESTS_BUILDDIR', meson.current_build_dir()) + +test('pygobject-test-suite', python, + args : [join_paths(meson.current_source_dir(), 'runtests.py')], + env : envdata, + timeout : 90) diff --git a/tests/org.gnome.test.gschema.xml b/tests/org.gnome.test.gschema.xml index eb9aab8..0c1e5c4 100644 --- a/tests/org.gnome.test.gschema.xml +++ b/tests/org.gnome.test.gschema.xml @@ -21,6 +21,10 @@ <key name="test-enum" enum="org.gnome.test.FruitType"> <default>'banana'</default> </key> + <key name="test-range" type="i"> + <range min="7" max="65535"/> + <default>123</default> + </key> </schema> <schema id="org.gnome.nopathtest"> diff --git a/tests/regressextra.c b/tests/regressextra.c new file mode 100644 index 0000000..41fdb84 --- /dev/null +++ b/tests/regressextra.c @@ -0,0 +1,416 @@ +#include "regress.h" +#include "regressextra.h" + +#include <glib-object.h> + +struct _RegressTestBoxedCWrapper +{ + RegressTestBoxedC * cptr; +}; + +RegressTestBoxedCWrapper * +regress_test_boxed_c_wrapper_new (void) +{ + RegressTestBoxedCWrapper *boxed; + boxed = g_slice_new (RegressTestBoxedCWrapper); + boxed->cptr = regress_test_boxed_c_new (); + return boxed; +} + +RegressTestBoxedCWrapper * +regress_test_boxed_c_wrapper_copy (RegressTestBoxedCWrapper *self) +{ + RegressTestBoxedCWrapper *ret_boxed; + ret_boxed = g_slice_new (RegressTestBoxedCWrapper); + ret_boxed->cptr = g_boxed_copy (regress_test_boxed_c_get_type(), self->cptr); + return ret_boxed; +} + +static void +regress_test_boxed_c_wrapper_free (RegressTestBoxedCWrapper *boxed) +{ + g_boxed_free (regress_test_boxed_c_get_type(), boxed->cptr); + g_slice_free (RegressTestBoxedCWrapper, boxed); +} + +G_DEFINE_BOXED_TYPE(RegressTestBoxedCWrapper, + regress_test_boxed_c_wrapper, + regress_test_boxed_c_wrapper_copy, + regress_test_boxed_c_wrapper_free); + +/** + * regress_test_boxed_c_wrapper_get + * @self: a #RegressTestBoxedCWrapper objects + * + * Returns: (transfer none): associated #RegressTestBoxedC +**/ +RegressTestBoxedC * +regress_test_boxed_c_wrapper_get (RegressTestBoxedCWrapper *self) +{ + return self->cptr; +} + +/** + * regress_test_array_fixed_boxed_none_out + * @objs: (out) (array fixed-size=2) (transfer none): An array of #RegressTestBoxedC +**/ +void +regress_test_array_fixed_boxed_none_out (RegressTestBoxedC ***objs) +{ + static RegressTestBoxedC **arr; + + if (arr == NULL) { + arr = g_new0 (RegressTestBoxedC *, 3); + arr[0] = regress_test_boxed_c_new (); + arr[1] = regress_test_boxed_c_new (); + } + + *objs = arr; +} + +/** + * regress_test_gvalue_out_boxed: + * @value: (out) (transfer full): the output gvalue + * @init: (in): the initialisation value +**/ +void +regress_test_gvalue_out_boxed (GValue *value, int init) +{ + RegressTestBoxed rtb; + GValue v = G_VALUE_INIT; + + memset(&rtb, 0, sizeof (rtb)); + rtb.some_int8 = init; + g_value_init (&v, REGRESS_TEST_TYPE_BOXED); + g_value_set_boxed (&v, &rtb); + *value = v; +} + +/** + * regress_test_glist_boxed_none_return + * Return value: (element-type RegressTestBoxedC) (transfer none): +**/ +GList * +regress_test_glist_boxed_none_return (guint count) +{ + static GList *list = NULL; + if (!list) { + while (count > 0) { + list = g_list_prepend (list, regress_test_boxed_c_new ()); + count--; + } + } + + return list; +} + +/** + * regress_test_glist_boxed_full_return + * Return value: (element-type RegressTestBoxedC) (transfer full): +**/ +GList * +regress_test_glist_boxed_full_return (guint count) +{ + GList *list = NULL; + while (count > 0) { + list = g_list_prepend (list, regress_test_boxed_c_new ()); + count--; + } + return list; +} + + +#ifndef _GI_DISABLE_CAIRO + +/** + * regress_test_cairo_context_none_return: + * + * Returns: (transfer none): + */ +cairo_t * +regress_test_cairo_context_none_return (void) +{ + static cairo_t *cr; + + if (cr == NULL) { + cairo_surface_t *surface; + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); + cr = cairo_create (surface); + cairo_surface_destroy (surface); + } + + return cr; +} + +/** + * regress_test_cairo_context_full_in: + * @context: (transfer full): + */ +void +regress_test_cairo_context_full_in (cairo_t *context) +{ + cairo_destroy (context); +} + + +/** + * regress_test_cairo_path_full_return: + * + * Returns: (transfer full): + */ +cairo_path_t * +regress_test_cairo_path_full_return (void) +{ + cairo_t *cr = regress_test_cairo_context_none_return (); + + return cairo_copy_path (cr); +} + +/** + * regress_test_cairo_path_none_in: + * @path: (transfer none): + */ +void +regress_test_cairo_path_none_in (cairo_path_t *path) +{ + cairo_t *cr = regress_test_cairo_context_full_return (); + cairo_append_path (cr, path); + g_assert (cairo_status (cr) == CAIRO_STATUS_SUCCESS); + cairo_destroy (cr); +} + +/** + * regress_test_cairo_path_full_in_full_return: + * @path: (transfer full): + * + * Returns: (transfer full): + */ +cairo_path_t * +regress_test_cairo_path_full_in_full_return (cairo_path_t *path) +{ + return path; +} + +/** + * regress_test_cairo_pattern_full_in: + * @pattern: (transfer full): + */ +void +regress_test_cairo_pattern_full_in (cairo_pattern_t *pattern) +{ + cairo_pattern_destroy (pattern); +} + +/** + * regress_test_cairo_pattern_none_in: + * @pattern: (transfer none): + */ +void +regress_test_cairo_pattern_none_in (cairo_pattern_t *pattern) +{ + cairo_t *cr = regress_test_cairo_context_full_return (); + cairo_set_source (cr, pattern); + g_assert (cairo_status (cr) == CAIRO_STATUS_SUCCESS); + cairo_destroy (cr); +} + +/** + * regress_test_cairo_pattern_none_return: + * + * Returns: (transfer none): + */ +cairo_pattern_t* +regress_test_cairo_pattern_none_return (void) +{ + static cairo_pattern_t *pattern; + + if (pattern == NULL) { + pattern = cairo_pattern_create_rgb(0.1, 0.2, 0.3); + } + + return pattern; +} + +/** + * regress_test_cairo_pattern_full_return: + * + * Returns: (transfer full): + */ +cairo_pattern_t * +regress_test_cairo_pattern_full_return (void) +{ + cairo_pattern_t *pattern = cairo_pattern_create_rgb(0.5, 0.6, 0.7); + return pattern; +} + +/** + * regress_test_cairo_region_full_in: + * @region: (transfer full): + */ +void +regress_test_cairo_region_full_in (cairo_region_t *region) +{ + cairo_region_destroy (region); +} + +/** + * regress_test_cairo_surface_full_in: + * @surface: (transfer full): + */ +void +regress_test_cairo_surface_full_in (cairo_surface_t *surface) +{ + g_assert (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32); + g_assert (cairo_image_surface_get_width (surface) == 10); + g_assert (cairo_image_surface_get_height (surface) == 10); + cairo_surface_destroy (surface); +} + +/** + * regress_test_cairo_font_options_full_return: + * + * Returns: (transfer full): + */ +cairo_font_options_t * +regress_test_cairo_font_options_full_return (void) +{ + return cairo_font_options_create (); +} + +/** + * regress_test_cairo_font_options_none_return: + * + * Returns: (transfer none): + */ +cairo_font_options_t * +regress_test_cairo_font_options_none_return (void) +{ + static cairo_font_options_t *options; + + if (options == NULL) + options = cairo_font_options_create (); + + return options; +} + +/** + * regress_test_cairo_font_options_full_in: + * @options: (transfer full): + */ +void +regress_test_cairo_font_options_full_in (cairo_font_options_t *options) +{ + cairo_font_options_destroy (options); +} + +/** + * regress_test_cairo_font_options_none_in: + * @options: (transfer none): + */ +void +regress_test_cairo_font_options_none_in (cairo_font_options_t *options) +{ +} + + +/** + * regress_test_cairo_matrix_none_in: + * @matrix: (transfer none): + */ +void +regress_test_cairo_matrix_none_in (const cairo_matrix_t *matrix) +{ + cairo_matrix_t m = *matrix; + g_assert (m.x0 == 0); + g_assert (m.y0 == 0); + g_assert (m.xx == 1); + g_assert (m.xy == 0); + g_assert (m.yy == 1); + g_assert (m.yx == 0); +} + +/** + * regress_test_cairo_matrix_none_return: + * Returns: (transfer none): + */ +cairo_matrix_t * +regress_test_cairo_matrix_none_return (void) +{ + static cairo_matrix_t matrix; + cairo_matrix_init_identity (&matrix); + return &matrix; +} + +/** + * regress_test_cairo_matrix_out_caller_allocates: + * @matrix: (out): + */ +void +regress_test_cairo_matrix_out_caller_allocates (cairo_matrix_t *matrix) +{ + cairo_matrix_t m; + cairo_matrix_init_identity (&m); + *matrix = m; +} + +#endif + +G_DEFINE_TYPE (RegressTestAction, regress_test_action, G_TYPE_INITIALLY_UNOWNED) + +enum +{ + SIGNAL_0, + ACTION_SIGNAL, + ACTION2_SIGNAL, + LAST_SIGNAL +}; + +static guint regress_test_action_signals[LAST_SIGNAL] = { 0 }; + +static RegressTestAction * +regress_test_action_do_action (RegressTestAction *self) +{ + RegressTestAction *ret = g_object_new (regress_test_action_get_type (), NULL); + + return ret; +} + +static RegressTestAction * +regress_test_action_do_action2 (RegressTestAction *self) +{ + return NULL; +} + +static void +regress_test_action_init (RegressTestAction *self) +{ +} + +static void regress_test_action_class_init (RegressTestActionClass *klass) +{ + /** + * RegressTestAction::action: + * + * An action signal. + * + * Returns: (transfer full): another #RegressTestAction + */ + regress_test_action_signals[ACTION_SIGNAL] = + g_signal_new_class_handler ("action", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (regress_test_action_do_action), NULL, NULL, + NULL, regress_test_action_get_type (), 0); + + /** + * RegressTestAction::action2: + * + * Another action signal. + * + * Returns: (transfer full): another #RegressTestAction + */ + regress_test_action_signals[ACTION2_SIGNAL] = + g_signal_new_class_handler ("action2", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (regress_test_action_do_action2), NULL, NULL, + NULL, regress_test_action_get_type (), 0); +} diff --git a/tests/regressextra.h b/tests/regressextra.h new file mode 100644 index 0000000..8834eb7 --- /dev/null +++ b/tests/regressextra.h @@ -0,0 +1,82 @@ +#ifndef REGRESS_EXTRA_H +#define REGRESS_EXTRA_H + +#include <glib-object.h> + +typedef struct _RegressTestBoxedC RegressTestBoxedC; +typedef struct _RegressTestBoxedCWrapper RegressTestBoxedCWrapper; + +_GI_TEST_EXTERN +GType regress_test_boxed_c_wrapper_get_type (void); + +_GI_TEST_EXTERN +RegressTestBoxedCWrapper *regress_test_boxed_c_wrapper_new (void); +_GI_TEST_EXTERN +RegressTestBoxedCWrapper * regress_test_boxed_c_wrapper_copy (RegressTestBoxedCWrapper *self); +_GI_TEST_EXTERN +RegressTestBoxedC *regress_test_boxed_c_wrapper_get (RegressTestBoxedCWrapper *self); + +_GI_TEST_EXTERN +void regress_test_array_fixed_boxed_none_out (RegressTestBoxedC ***objs); +_GI_TEST_EXTERN +void regress_test_gvalue_out_boxed (GValue *value, int init); +_GI_TEST_EXTERN +GList *regress_test_glist_boxed_none_return (guint count); +_GI_TEST_EXTERN +GList *regress_test_glist_boxed_full_return (guint count); + +#ifndef _GI_DISABLE_CAIRO + +_GI_TEST_EXTERN +cairo_t *regress_test_cairo_context_none_return (void); +_GI_TEST_EXTERN +void regress_test_cairo_context_full_in (cairo_t *context); +_GI_TEST_EXTERN +cairo_path_t *regress_test_cairo_path_full_return (void); +_GI_TEST_EXTERN +void regress_test_cairo_path_none_in (cairo_path_t *path); +_GI_TEST_EXTERN +cairo_path_t * regress_test_cairo_path_full_in_full_return (cairo_path_t *path); +_GI_TEST_EXTERN +void regress_test_cairo_pattern_full_in (cairo_pattern_t *pattern); +_GI_TEST_EXTERN +void regress_test_cairo_pattern_none_in (cairo_pattern_t *pattern); +_GI_TEST_EXTERN +cairo_pattern_t* regress_test_cairo_pattern_none_return (void); +_GI_TEST_EXTERN +cairo_pattern_t * regress_test_cairo_pattern_full_return (void); +_GI_TEST_EXTERN +cairo_font_options_t *regress_test_cairo_font_options_full_return (void); +_GI_TEST_EXTERN +cairo_font_options_t *regress_test_cairo_font_options_none_return (void); +_GI_TEST_EXTERN +void regress_test_cairo_font_options_full_in (cairo_font_options_t *options); +_GI_TEST_EXTERN +void regress_test_cairo_font_options_none_in (cairo_font_options_t *options); +_GI_TEST_EXTERN +void regress_test_cairo_region_full_in (cairo_region_t *region); +_GI_TEST_EXTERN +void regress_test_cairo_surface_full_in (cairo_surface_t *surface); +_GI_TEST_EXTERN +void regress_test_cairo_matrix_none_in (const cairo_matrix_t *matrix); +_GI_TEST_EXTERN +cairo_matrix_t *regress_test_cairo_matrix_none_return (void); +_GI_TEST_EXTERN +void regress_test_cairo_matrix_out_caller_allocates (cairo_matrix_t *matrix); + +#endif + +/* RegressTestAction */ + +typedef struct { + GInitiallyUnowned parent; +} RegressTestAction; + +typedef struct { + GInitiallyUnownedClass parent_class; +} RegressTestActionClass; + +_GI_TEST_EXTERN +GType regress_test_action_get_type (void); + +#endif /* REGRESS_EXTRA_H */ diff --git a/tests/runtests-windows.py b/tests/runtests-windows.py deleted file mode 100644 index 36ab9e1..0000000 --- a/tests/runtests-windows.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - - -import os -import sys -import glob -import unittest - -mydir = os.path.dirname(os.path.abspath(__file__)) -tests_builddir = os.path.abspath(os.environ.get('TESTS_BUILDDIR', os.path.dirname(__file__))) -builddir = os.path.dirname(tests_builddir) - -# we have to do this here instead of Makefile.am so that the implicitly added -# directory for the source file comes after the builddir -sys.path.insert(0, tests_builddir) -sys.path.insert(0, builddir) - -os.environ['PYGTK_USE_GIL_STATE_API'] = '' -sys.argv.append('--g-fatal-warnings') - -from gi.repository import GObject -GObject.threads_init() - - -SKIP_FILES = ['runtests', - 'test_mainloop', # no os.fork on windows - 'test_subprocess'] # blocks on testChildWatch - - -if __name__ == '__main__': - testdir = os.path.split(os.path.abspath(__file__))[0] - os.chdir(testdir) - - def gettestnames(): - files = glob.glob('*.py') - names = map(lambda x: x[:-3], files) - map(names.remove, SKIP_FILES) - return names - - suite = unittest.TestSuite() - loader = unittest.TestLoader() - - for name in gettestnames(): - try: - suite.addTest(loader.loadTestsFromName(name)) - except Exception, e: - print 'Could not load %s: %s' % (name, e) - - testRunner = unittest.TextTestRunner() - testRunner.verbosity = 2 - testRunner.run(suite) diff --git a/tests/runtests.py b/tests/runtests.py index 6085ff9..721b5d8 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -2,61 +2,43 @@ # -*- Mode: Python -*- import os -import glob import sys -import unittest - -# this was renamed in Python 3, provide backwards compatible name -if sys.version_info[:2] == (2, 7): - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - -if '--help' in sys.argv: - print("Usage: ./runtests.py <testfiles>") - sys.exit(0) - -mydir = os.path.dirname(os.path.abspath(__file__)) -tests_builddir = os.path.abspath(os.environ.get('TESTS_BUILDDIR', os.path.dirname(__file__))) -builddir = os.path.dirname(tests_builddir) - -# we have to do this here instead of Makefile.am so that the implicitly added -# directory for the source file comes after the builddir -sys.path.insert(0, tests_builddir) -sys.path.insert(0, builddir) - -# force untranslated messages, as we check for them in some tests -os.environ['LC_MESSAGES'] = 'C' -os.environ['G_DEBUG'] = 'fatal-warnings fatal-criticals' - -# make Gio able to find our gschemas.compiled in tests/. This needs to be set -# before importing Gio. Support a separate build tree, so look in build dir -# first. -os.environ['GSETTINGS_BACKEND'] = 'memory' -os.environ['GSETTINGS_SCHEMA_DIR'] = tests_builddir -os.environ['G_FILENAME_ENCODING'] = 'UTF-8' - -# Load tests. -if 'TEST_NAMES' in os.environ: - names = os.environ['TEST_NAMES'].split() -elif 'TEST_FILES' in os.environ: - names = [] - for filename in os.environ['TEST_FILES'].split(): - names.append(filename[:-3]) -elif len(sys.argv) > 1: - names = [] - for filename in sys.argv[1:]: - names.append(filename.replace('.py', '')) -else: - names = [] - for filename in glob.iglob(os.path.join(mydir, 'test_*.py')): - names.append(os.path.basename(filename)[:-3]) - -loader = unittest.TestLoader() -suite = loader.loadTestsFromNames(names) - - -# Run tests. -runner = unittest.TextTestRunner(verbosity=2) -result = runner.run(suite) -if not result.wasSuccessful(): - sys.exit(1) # exit code so "make check" reports error +import pytest + + +def main(argv): + if '--help' in argv: + print("Usage: ./runtests.py <testfiles>") + return + + mydir = os.path.dirname(os.path.abspath(__file__)) + + verbosity_args = [] + + if 'PYGI_TEST_VERBOSE' in os.environ: + verbosity_args += ['--capture=no'] + + if 'TEST_NAMES' in os.environ: + names = os.environ['TEST_NAMES'].split() + elif 'TEST_FILES' in os.environ: + names = [] + for filename in os.environ['TEST_FILES'].split(): + names.append(filename[:-3]) + elif len(argv) > 1: + names = [] + for filename in argv[1:]: + names.append(filename.replace('.py', '')) + else: + return pytest.main([mydir] + verbosity_args) + + def unittest_to_pytest_name(name): + parts = name.split(".") + parts[0] = os.path.join(mydir, parts[0] + ".py") + return "::".join(parts) + + return pytest.main([unittest_to_pytest_name(n) for n in names] + verbosity_args) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tests/te_ST@nouppera b/tests/te_ST@nouppera deleted file mode 100644 index a511e90..0000000 --- a/tests/te_ST@nouppera +++ /dev/null @@ -1,50 +0,0 @@ -LC_IDENTIFICATION -title "pygobject test locale" -END LC_IDENTIFICATION - -LC_COLLATE -copy "POSIX" -END LC_COLLATE - -LC_CTYPE -# a → a, other characters normally -toupper (<U0061>,<U0061>); (<U0062>,<U0042>); (<U0063>,<U0043>); \ - (<U0076>,<U0056>); (<U006C>,<U004C>); (<U0075>,<U0055>); \ - (<U0065>,<U0045>); -END LC_CTYPE - -LC_MESSAGES -copy "en_US" -END LC_MESSAGES - -LC_MONETARY -copy "en_US" -END LC_MONETARY - -LC_NUMERIC -copy "POSIX" -END LC_NUMERIC - -LC_TIME -copy "POSIX" -END LC_TIME - -LC_PAPER -copy "en_US" -END LC_PAPER - -LC_TELEPHONE -copy "en_US" -END LC_TELEPHONE - -LC_MEASUREMENT -copy "en_US" -END LC_MEASUREMENT - -LC_NAME -copy "en_US" -END LC_NAME - -LC_ADDRESS -copy "en_US" -END LC_ADDRESS diff --git a/tests/test_atoms.py b/tests/test_atoms.py index 1561dbd..a74db38 100644 --- a/tests/test_atoms.py +++ b/tests/test_atoms.py @@ -1,13 +1,28 @@ +import os import unittest try: - from gi.repository import Atk, Gdk, Gtk - (Atk, Gdk) # pyflakes -except: + from gi.repository import Gtk, Atk, Gdk +except ImportError: Gdk = None + Atk = None + Gtk = None + +from .helper import capture_glib_deprecation_warnings + + +def is_X11(): + try: + from gi.repository import Gdk, GdkX11 + except ImportError: + return False + + display = Gdk.Display.get_default() + return isinstance(display, GdkX11.X11Display) @unittest.skipUnless(Gdk, 'Gdk not available') +@unittest.skipIf(Gdk._version == "4.0", 'Gdk4 doesn\'t have GdkAtom') class TestGdkAtom(unittest.TestCase): def test_create(self): atom = Gdk.Atom.intern('my_string', False) @@ -20,11 +35,15 @@ class TestGdkAtom(unittest.TestCase): self.assertEqual(str(Gdk.SELECTION_CLIPBOARD), 'CLIPBOARD') def test_repr(self): + # __repr__ should generate a string which is parsable when possible + # http://docs.python.org/2/reference/datamodel.html#object.__repr__ atom = Gdk.Atom.intern('my_string', False) - self.assertEqual(repr(atom), 'Gdk.Atom<my_string>') + self.assertEqual(repr(atom), 'Gdk.Atom.intern("my_string", False)') + self.assertEqual(eval(repr(atom)), atom) - self.assertEqual(repr(Gdk.SELECTION_CLIPBOARD), 'Gdk.Atom<CLIPBOARD>') + self.assertEqual(repr(Gdk.SELECTION_CLIPBOARD), 'Gdk.Atom.intern("CLIPBOARD", False)') + @unittest.skipUnless(os.name != "nt", "not on Windows") def test_in_single(self): a_selection = Gdk.Atom.intern('test_clipboard', False) clipboard = Gtk.Clipboard.get(a_selection) @@ -48,6 +67,7 @@ class TestGdkAtom(unittest.TestCase): self.assertTrue(Gtk.targets_include_image([a_jpeg], False)) self.assertTrue(Gtk.targets_include_image([a_jpeg, a_plain], False)) + @unittest.skipUnless(is_X11(), "only on X11") def test_out_array(self): a_selection = Gdk.Atom.intern('my_clipboard', False) clipboard = Gtk.Clipboard.get(a_selection) @@ -67,11 +87,13 @@ class TestGdkAtom(unittest.TestCase): self.assertFalse(None in names, names) self.assertTrue('TEXT' in names, names) + @unittest.skipUnless(is_X11(), "only on X11") + @unittest.skipIf(not Gdk or Gdk._version == "4.0", "not in gdk4") def test_out_glist(self): display = Gdk.Display.get_default() - dm = display.get_device_manager() - device = dm.get_client_pointer() + with capture_glib_deprecation_warnings(): + dm = display.get_device_manager() + device = dm.get_client_pointer() axes = device.list_axes() - axes_names = [atom.name() for atom in axes] - self.assertNotEqual(axes_names, []) - self.assertTrue('Rel X' in axes_names) + axes_names = [atom.name() for atom in axes if atom is not None] + assert all(isinstance(name, str) for name in axes_names) diff --git a/tests/test_cairo.py b/tests/test_cairo.py new file mode 100644 index 0000000..d73037e --- /dev/null +++ b/tests/test_cairo.py @@ -0,0 +1,308 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab + +import unittest +import pytest + +import gi + +try: + gi.require_foreign('cairo') + import cairo + has_cairo = True +except ImportError: + has_cairo = False + +has_region = has_cairo and hasattr(cairo, "Region") + +try: + from gi.repository import Gtk, Gdk + Gtk, Gdk # pyflakes +except: + Gtk = None + Gdk = None + +from gi.repository import GObject, Regress + + +@unittest.skipUnless(has_cairo, 'built without cairo support') +class Test(unittest.TestCase): + + def test_gvalue_converters(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + matrix = cairo.Matrix() + objects = { + 'CairoContext': context, + 'CairoSurface': surface, + 'CairoFontFace': context.get_font_face(), + 'CairoScaledFont': context.get_scaled_font(), + 'CairoPattern': context.get_source(), + 'CairoMatrix': matrix, + } + for type_name, cairo_obj in objects.items(): + gtype = GObject.type_from_name(type_name) + v = GObject.Value() + assert v.init(gtype) is None + assert v.get_value() is None + v.set_value(None) + assert v.get_value() is None + v.set_value(cairo_obj) + assert v.get_value() == cairo_obj + + def test_cairo_context(self): + context = Regress.test_cairo_context_full_return() + self.assertTrue(isinstance(context, cairo.Context)) + + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + Regress.test_cairo_context_none_in(context) + + def test_cairo_context_full_in(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + Regress.test_cairo_context_full_in(context) + + with pytest.raises(TypeError): + Regress.test_cairo_context_full_in(object()) + + def test_cairo_context_none_return(self): + context = Regress.test_cairo_context_none_return() + self.assertTrue(isinstance(context, cairo.Context)) + + def test_cairo_path_full_return(self): + path = Regress.test_cairo_path_full_return() + if hasattr(cairo, "Path"): # pycairo 1.15.1+ + assert isinstance(path, cairo.Path) + + def test_cairo_path_none_in(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + path = context.copy_path() + Regress.test_cairo_path_none_in(path) + surface.finish() + + with pytest.raises(TypeError): + Regress.test_cairo_path_none_in(object()) + + def test_cairo_path_full_in_full_return(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + context.move_to(10, 10) + context.curve_to(10, 10, 3, 4, 5, 6) + path = context.copy_path() + new_path = Regress.test_cairo_path_full_in_full_return(path) + assert list(path) == list(new_path) + surface.finish() + + def test_cairo_font_options_full_return(self): + options = Regress.test_cairo_font_options_full_return() + assert isinstance(options, cairo.FontOptions) + + def test_cairo_font_options_none_return(self): + options = Regress.test_cairo_font_options_none_return() + assert isinstance(options, cairo.FontOptions) + + def test_cairo_font_options_full_in(self): + options = cairo.FontOptions() + Regress.test_cairo_font_options_full_in(options) + + with pytest.raises(TypeError): + Regress.test_cairo_font_options_full_in(object()) + + def test_cairo_font_options_none_in(self): + options = cairo.FontOptions() + Regress.test_cairo_font_options_none_in(options) + + def test_cairo_pattern_full_in(self): + pattern = cairo.SolidPattern(1, 1, 1, 1) + Regress.test_cairo_pattern_full_in(pattern) + + with pytest.raises(TypeError): + Regress.test_cairo_pattern_full_in(object()) + + def test_cairo_pattern_none_in(self): + pattern = cairo.SolidPattern(1, 1, 1, 1) + Regress.test_cairo_pattern_none_in(pattern) + + def test_cairo_pattern_full_return(self): + pattern = Regress.test_cairo_pattern_full_return() + self.assertTrue(isinstance(pattern, cairo.Pattern)) + self.assertTrue(isinstance(pattern, cairo.SolidPattern)) + + def test_cairo_pattern_none_return(self): + pattern = Regress.test_cairo_pattern_none_return() + self.assertTrue(isinstance(pattern, cairo.Pattern)) + self.assertTrue(isinstance(pattern, cairo.SolidPattern)) + + def test_cairo_region_full_in(self): + region = cairo.Region() + Regress.test_cairo_region_full_in(region) + + with pytest.raises(TypeError): + Regress.test_cairo_region_full_in(object()) + + def test_cairo_matrix_none_in(self): + matrix = cairo.Matrix() + Regress.test_cairo_matrix_none_in(matrix) + + with pytest.raises(TypeError): + Regress.test_cairo_matrix_none_in(object()) + + def test_cairo_matrix_none_return(self): + matrix = Regress.test_cairo_matrix_none_return() + assert matrix == cairo.Matrix() + + def test_cairo_matrix_out_caller_allocates(self): + matrix = Regress.test_cairo_matrix_out_caller_allocates() + assert matrix == cairo.Matrix() + + def test_cairo_surface(self): + surface = Regress.test_cairo_surface_none_return() + self.assertTrue(isinstance(surface, cairo.ImageSurface)) + self.assertTrue(isinstance(surface, cairo.Surface)) + self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32) + self.assertEqual(surface.get_width(), 10) + self.assertEqual(surface.get_height(), 10) + + surface = Regress.test_cairo_surface_full_return() + self.assertTrue(isinstance(surface, cairo.ImageSurface)) + self.assertTrue(isinstance(surface, cairo.Surface)) + self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32) + self.assertEqual(surface.get_width(), 10) + self.assertEqual(surface.get_height(), 10) + + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + Regress.test_cairo_surface_none_in(surface) + + surface = Regress.test_cairo_surface_full_out() + self.assertTrue(isinstance(surface, cairo.ImageSurface)) + self.assertTrue(isinstance(surface, cairo.Surface)) + self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32) + self.assertEqual(surface.get_width(), 10) + self.assertEqual(surface.get_height(), 10) + + def test_cairo_surface_full_in(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + Regress.test_cairo_surface_full_in(surface) + + with pytest.raises(TypeError): + Regress.test_cairo_surface_full_in(object()) + + def test_require_foreign(self): + self.assertEqual(gi.require_foreign('cairo'), None) + self.assertEqual(gi.require_foreign('cairo', 'Context'), None) + self.assertRaises(ImportError, gi.require_foreign, 'invalid_module') + self.assertRaises(ImportError, gi.require_foreign, 'invalid_module', 'invalid_symbol') + self.assertRaises(ImportError, gi.require_foreign, 'cairo', 'invalid_symbol') + + +@unittest.skipUnless(has_cairo, 'built without cairo support') +@unittest.skipUnless(has_region, 'built without cairo.Region support') +@unittest.skipUnless(Gdk, 'Gdk not available') +class TestRegion(unittest.TestCase): + + def test_region_to_py(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + context.paint() + region = Gdk.cairo_region_create_from_surface(surface) + r = region.get_extents() + self.assertEqual((r.height, r.width), (10, 10)) + + def test_region_from_py(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + region = cairo.Region(cairo.RectangleInt(0, 0, 42, 42)) + Gdk.cairo_region(context, region) + self.assertTrue("42" in repr(list(context.copy_path()))) + + +@unittest.skipUnless(has_cairo, 'built without cairo support') +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestPango(unittest.TestCase): + + def test_cairo_font_options(self): + window = Gtk.Window() + if Gtk._version == "4.0": + window.set_font_options(cairo.FontOptions()) + font_opts = window.get_font_options() + else: + screen = window.get_screen() + font_opts = screen.get_font_options() + assert font_opts is not None + self.assertTrue(isinstance(font_opts.get_subpixel_order(), int)) + + +if has_cairo: + from gi.repository import cairo as CairoGObject + + # Use PyGI signals to test non-introspected foreign marshaling. + class CairoSignalTester(GObject.Object): + sig_context = GObject.Signal(arg_types=[CairoGObject.Context]) + sig_surface = GObject.Signal(arg_types=[CairoGObject.Surface]) + sig_font_face = GObject.Signal(arg_types=[CairoGObject.FontFace]) + sig_scaled_font = GObject.Signal(arg_types=[CairoGObject.ScaledFont]) + sig_pattern = GObject.Signal(arg_types=[CairoGObject.Pattern]) + + +@unittest.skipUnless(has_cairo, 'built without cairo support') +class TestSignalMarshaling(unittest.TestCase): + # Tests round tripping of cairo objects through non-introspected signals. + + def setUp(self): + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + self.context = cairo.Context(self.surface) + self.tester = CairoSignalTester() + + def pass_object_through_signal(self, obj, signal): + """Pass the given `obj` through the `signal` emission storing the + `obj` passed through the signal and returning it.""" + passthrough_result = [] + + def callback(instance, passthrough): + passthrough_result.append(passthrough) + + signal.connect(callback) + signal.emit(obj) + + return passthrough_result[0] + + def test_context(self): + result = self.pass_object_through_signal(self.context, self.tester.sig_context) + self.assertTrue(isinstance(result, cairo.Context)) + + with pytest.raises(TypeError): + self.pass_object_through_signal(object(), self.tester.sig_context) + + def test_surface(self): + result = self.pass_object_through_signal(self.surface, self.tester.sig_surface) + self.assertTrue(isinstance(result, cairo.Surface)) + + def test_font_face(self): + font_face = self.context.get_font_face() + result = self.pass_object_through_signal(font_face, self.tester.sig_font_face) + self.assertTrue(isinstance(result, cairo.FontFace)) + + with pytest.raises(TypeError): + self.pass_object_through_signal(object(), self.tester.sig_font_face) + + def test_scaled_font(self): + scaled_font = cairo.ScaledFont(self.context.get_font_face(), + cairo.Matrix(), + cairo.Matrix(), + self.context.get_font_options()) + result = self.pass_object_through_signal(scaled_font, self.tester.sig_scaled_font) + self.assertTrue(isinstance(result, cairo.ScaledFont)) + + with pytest.raises(TypeError): + result = self.pass_object_through_signal(object(), self.tester.sig_scaled_font) + + def test_pattern(self): + pattern = cairo.SolidPattern(1, 1, 1, 1) + result = self.pass_object_through_signal(pattern, self.tester.sig_pattern) + self.assertTrue(isinstance(result, cairo.Pattern)) + self.assertTrue(isinstance(result, cairo.SolidPattern)) + + with pytest.raises(TypeError): + result = self.pass_object_through_signal(object(), self.tester.sig_pattern) diff --git a/tests/test_docstring.py b/tests/test_docstring.py index 1628295..527d2a1 100644 --- a/tests/test_docstring.py +++ b/tests/test_docstring.py @@ -1,7 +1,17 @@ import unittest import gi.docstring + +from gi.repository import Regress from gi.repository import GIMarshallingTests +from gi.repository import Gio +from gi.repository import GObject +from gi.repository import GLib + +try: + from gi.repository import Gtk +except ImportError: + Gtk = None class Test(unittest.TestCase): @@ -20,26 +30,6 @@ class Test(unittest.TestCase): self.assertEqual(gi.docstring.get_doc_string_generator(), old_func) - def test_split_args_multi_out(self): - in_args, out_args = gi.docstring.split_function_info_args(GIMarshallingTests.int_out_out) - self.assertEqual(len(in_args), 0) - self.assertEqual(len(out_args), 2) - self.assertEqual(out_args[0].get_pytype_hint(), 'int') - self.assertEqual(out_args[1].get_pytype_hint(), 'int') - - def test_split_args_inout(self): - in_args, out_args = gi.docstring.split_function_info_args(GIMarshallingTests.long_inout_max_min) - self.assertEqual(len(in_args), 1) - self.assertEqual(len(out_args), 1) - self.assertEqual(in_args[0].get_name(), out_args[0].get_name()) - self.assertEqual(in_args[0].get_pytype_hint(), out_args[0].get_pytype_hint()) - - def test_split_args_none(self): - obj = GIMarshallingTests.Object(int=33) - in_args, out_args = gi.docstring.split_function_info_args(obj.none_inout) - self.assertEqual(len(in_args), 1) - self.assertEqual(len(out_args), 1) - def test_final_signature_with_full_inout(self): self.assertEqual(GIMarshallingTests.Object.full_inout.__doc__, 'full_inout(object:GIMarshallingTests.Object) -> object:GIMarshallingTests.Object') @@ -47,3 +37,102 @@ class Test(unittest.TestCase): def test_overridden_doc_is_not_clobbered(self): self.assertEqual(GIMarshallingTests.OverridesObject.method.__doc__, 'Overridden doc string.') + + def test_allow_none_with_user_data_defaults(self): + g_file_copy_doc = 'copy(self, destination:Gio.File, ' \ + 'flags:Gio.FileCopyFlags, ' \ + 'cancellable:Gio.Cancellable=None, ' \ + 'progress_callback:Gio.FileProgressCallback=None, ' \ + 'progress_callback_data=None) -> bool' + + self.assertEqual(Gio.File.copy.__doc__, g_file_copy_doc) + + def test_array_length_arg(self): + self.assertEqual(GIMarshallingTests.array_in.__doc__, + 'array_in(ints:list)') + + def test_init_function(self): + # This tests implicit array length args along with skipping a + # boolean return + self.assertEqual(GIMarshallingTests.init_function.__doc__, + 'init_function(argv:list=None) -> bool, argv:list') + + def test_boolean_return(self): + self.assertEqual(GIMarshallingTests.boolean_return_true.__doc__, + 'boolean_return_true() -> bool') + + @unittest.skipUnless((GLib.MAJOR_VERSION, GLib.MINOR_VERSION) >= (2, 42), + "nullable was added in newer glib/gi") + # https://bugzilla.gnome.org/show_bug.cgi?id=740301 + def test_may_return_none(self): + self.assertEqual(Gio.File.get_basename.__doc__, + 'get_basename(self) -> str or None') + + def test_class_doc_constructors(self): + doc = GIMarshallingTests.Object.__doc__ + self.assertTrue('new(int_:int)' in doc) + + def test_struct_doc_constructors(self): + doc = GIMarshallingTests.BoxedStruct.__doc__ + self.assertTrue('new()' in doc) + self.assertTrue('BoxedStruct()' in doc) + + def test_private_struct_constructors(self): + # Structs without a size or constructor should have no constructor docs. + doc = Regress.TestBoxedPrivate.__doc__ + self.assertEqual(doc, '') + + def test_array_inout_etc(self): + self.assertEqual(GIMarshallingTests.array_inout_etc.__doc__, + 'array_inout_etc(first:int, ints:list, last:int) -> ints:list, sum:int') + + def test_array_out_etc(self): + self.assertEqual(GIMarshallingTests.array_out_etc.__doc__, + 'array_out_etc(first:int, last:int) -> ints:list, sum:int') + + @unittest.skipUnless(Gtk, 'no Gtk') + def test_shared_array_length_with_prior_out_arg(self): + # Test the 'iter' out argument does not effect length argument skipping. + self.assertRegex( + Gtk.ListStore.insert_with_valuesv.__doc__, + 'insert_with_values.*\\(self, position:int, columns:list, values:list\\) -> iter:Gtk.TreeIter') + + def test_sub_class_doc(self): + class A(GObject.Object): + """first doc""" + pass + + class B(A): + """second doc""" + pass + + self.assertEqual(A.__doc__, "first doc") + self.assertEqual(B.__doc__, "second doc") + + def test_sub_class_no_doc(self): + class A(GObject.Object): + pass + + class B(A): + """sub-class doc""" + + self.assertEqual(A.__doc__, None) + self.assertEqual(B.__doc__, "sub-class doc") + + @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=734926 + def test_sub_class_doc_setattr(self): + class A(GObject.Object): + pass + + class B(A): + pass + + A.__doc__ = 'custom doc' + + self.assertEqual(A.__doc__, "custom doc") + self.assertEqual(B.__doc__, "custom doc") + + def test_return_array_with_length_argument(self): + self.assertEqual( + GIMarshallingTests.enum_array_return_type.__doc__, + "enum_array_return_type() -> list") diff --git a/tests/test_error.py b/tests/test_error.py new file mode 100644 index 0000000..6bcad38 --- /dev/null +++ b/tests/test_error.py @@ -0,0 +1,157 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab +# +# test_error.py: Tests for GError wrapper implementation +# +# Copyright (C) 2012 Will Thompson +# Copyright (C) 2013 Martin Pitt +# Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + +import unittest +import pickle + +from gi.repository import GLib +from gi.repository import GIMarshallingTests + + +class TestType(unittest.TestCase): + def test_attributes(self): + e = GLib.Error('test message', 'mydomain', 42) + self.assertEqual(e.message, 'test message') + self.assertEqual(e.domain, 'mydomain') + self.assertEqual(e.code, 42) + + def test_new_literal(self): + mydomain = GLib.quark_from_string('mydomain') + e = GLib.Error.new_literal(mydomain, 'test message', 42) + self.assertEqual(e.message, 'test message') + self.assertEqual(e.domain, 'mydomain') + self.assertEqual(e.code, 42) + + def test_matches(self): + mydomain = GLib.quark_from_string('mydomain') + notmydomain = GLib.quark_from_string('notmydomain') + e = GLib.Error('test message', 'mydomain', 42) + self.assertTrue(e.matches(mydomain, 42)) + self.assertFalse(e.matches(notmydomain, 42)) + self.assertFalse(e.matches(mydomain, 40)) + + def test_str(self): + e = GLib.Error('test message', 'mydomain', 42) + self.assertEqual(str(e), + 'mydomain: test message (42)') + + def test_repr(self): + e = GLib.Error('test message', 'mydomain', 42) + self.assertEqual(repr(e), + "GLib.Error('test message', 'mydomain', 42)") + + def test_inheritance(self): + self.assertTrue(issubclass(GLib.Error, RuntimeError)) + + def test_pickle(self): + + def check_pickle(e): + assert isinstance(e, GLib.Error) + new_e = pickle.loads(pickle.dumps(e)) + assert type(new_e) is type(e) + assert repr(e) == repr(new_e) + + e = GLib.Error('test message', 'mydomain', 42) + check_pickle(e) + + try: + GLib.file_get_contents("") + except Exception as e: + check_pickle(e) + + +class ObjectWithVFuncException(GIMarshallingTests.Object): + def do_vfunc_meth_with_err(self, x): + if x == 42: + return True + + raise GLib.Error('unexpected value %d' % x, 'mydomain', 42) + + +class TestMarshalling(unittest.TestCase): + def test_array_in_crash(self): + # Previously there was a bug in invoke, in which C arrays were unwrapped + # from inside GArrays to be passed to the C function. But when a GError was + # set, invoke would attempt to free the C array as if it were a GArray. + # This crash is only for C arrays. It does not happen for C functions which + # take in GArrays. See https://bugzilla.gnome.org/show_bug.cgi?id=642708 + self.assertRaises(GLib.Error, GIMarshallingTests.gerror_array_in, [1, 2, 3]) + + def test_out(self): + # See https://bugzilla.gnome.org/show_bug.cgi?id=666098 + error, debug = GIMarshallingTests.gerror_out() + + self.assertIsInstance(error, GLib.Error) + self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) + self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE) + self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) + self.assertEqual(debug, GIMarshallingTests.CONSTANT_GERROR_DEBUG_MESSAGE) + + def test_out_transfer_none(self): + # See https://bugzilla.gnome.org/show_bug.cgi?id=666098 + error, debug = GIMarshallingTests.gerror_out_transfer_none() + + self.assertIsInstance(error, GLib.Error) + self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) + self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE) + self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) + self.assertEqual(GIMarshallingTests.CONSTANT_GERROR_DEBUG_MESSAGE, debug) + + def test_return(self): + # See https://bugzilla.gnome.org/show_bug.cgi?id=666098 + error = GIMarshallingTests.gerror_return() + + self.assertIsInstance(error, GLib.Error) + self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) + self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE) + self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) + + def test_exception(self): + with self.assertRaises(GLib.Error) as context: + GIMarshallingTests.gerror() + + e = context.exception + self.assertEqual(e.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) + self.assertEqual(e.code, GIMarshallingTests.CONSTANT_GERROR_CODE) + self.assertEqual(e.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) + + def test_vfunc_no_exception(self): + obj = ObjectWithVFuncException() + self.assertTrue(obj.vfunc_meth_with_error(42)) + + def test_vfunc_gerror_exception(self): + obj = ObjectWithVFuncException() + with self.assertRaises(GLib.Error) as context: + obj.vfunc_meth_with_error(-1) + + e = context.exception + self.assertEqual(e.message, 'unexpected value -1') + self.assertEqual(e.domain, 'mydomain') + self.assertEqual(e.code, 42) + + def tests_compare_two_gerrors_in_gvalue(self): + error = GLib.Error.new_literal(1, "error", 1) + error1 = GLib.Error.new_literal(1, "error", 1) + + GIMarshallingTests.compare_two_gerrors_in_gvalue(error, error1) diff --git a/tests/test_everything.py b/tests/test_everything.py index b2f0528..0eaa522 100644 --- a/tests/test_everything.py +++ b/tests/test_everything.py @@ -1,5 +1,4 @@ # -*- Mode: Python; py-indent-offset: 4 -*- -# coding=utf-8 # vim: tabstop=4 shiftwidth=4 expandtab import unittest @@ -7,15 +6,16 @@ import traceback import ctypes import warnings import sys +import os +import re +import platform +import gc +import timeit +import random -try: - import cairo - has_cairo = True - from gi.repository import Regress as Everything -except ImportError: - has_cairo = False +import pytest -#import gi +from gi.repository import Regress as Everything from gi.repository import GObject from gi.repository import GLib from gi.repository import Gio @@ -26,11 +26,11 @@ try: except: Gtk = None -if sys.version_info < (3, 0): - UNICHAR = "\xe2\x99\xa5" - PY2_UNICODE_UNICHAR = unicode(UNICHAR, 'UTF-8') -else: - UNICHAR = "♥" +from .helper import capture_exceptions + + +const_str = b'const \xe2\x99\xa5 utf8'.decode('UTF-8') +noconst_str = 'non' + const_str class RawGList(ctypes.Structure): @@ -44,41 +44,15 @@ class RawGList(ctypes.Structure): return ctypes.POINTER(cls).from_address(id(obj) + offset) -@unittest.skipUnless(has_cairo, 'built without cairo support') -class TestEverything(unittest.TestCase): +class TestInstanceTransfer(unittest.TestCase): + + def test_main(self): + obj = Everything.TestObj() + for _ in range(10): + obj.instance_method_full() + - def test_cairo_context(self): - context = Everything.test_cairo_context_full_return() - self.assertTrue(isinstance(context, cairo.Context)) - - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) - context = cairo.Context(surface) - Everything.test_cairo_context_none_in(context) - - def test_cairo_surface(self): - surface = Everything.test_cairo_surface_none_return() - self.assertTrue(isinstance(surface, cairo.ImageSurface)) - self.assertTrue(isinstance(surface, cairo.Surface)) - self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32) - self.assertEqual(surface.get_width(), 10) - self.assertEqual(surface.get_height(), 10) - - surface = Everything.test_cairo_surface_full_return() - self.assertTrue(isinstance(surface, cairo.ImageSurface)) - self.assertTrue(isinstance(surface, cairo.Surface)) - self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32) - self.assertEqual(surface.get_width(), 10) - self.assertEqual(surface.get_height(), 10) - - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) - Everything.test_cairo_surface_none_in(surface) - - surface = Everything.test_cairo_surface_full_out() - self.assertTrue(isinstance(surface, cairo.ImageSurface)) - self.assertTrue(isinstance(surface, cairo.Surface)) - self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32) - self.assertEqual(surface.get_width(), 10) - self.assertEqual(surface.get_height(), 10) +class TestEverything(unittest.TestCase): def test_bool(self): self.assertEqual(Everything.test_boolean(False), False) @@ -90,142 +64,334 @@ class TestEverything(unittest.TestCase): self.assertEqual(Everything.test_boolean_false(False), False) def test_int8(self): - self.assertEqual(Everything.test_int8(GObject.G_MAXINT8), - GObject.G_MAXINT8) - self.assertEqual(Everything.test_int8(GObject.G_MININT8), - GObject.G_MININT8) - self.assertRaises(OverflowError, Everything.test_int8, GObject.G_MAXINT8 + 1) - - self.assertEqual(Everything.test_uint8(GObject.G_MAXUINT8), - GObject.G_MAXUINT8) + self.assertEqual(Everything.test_int8(GLib.MAXINT8), + GLib.MAXINT8) + self.assertEqual(Everything.test_int8(GLib.MININT8), + GLib.MININT8) + self.assertRaises(OverflowError, Everything.test_int8, GLib.MAXINT8 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXINT8 + 1, GLib.MININT8, GLib.MAXINT8)): + Everything.test_int8(GLib.MAXINT8 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXUINT64 * 2, GLib.MININT8, GLib.MAXINT8)): + Everything.test_int8(GLib.MAXUINT64 * 2) + + def test_uint8(self): + self.assertEqual(Everything.test_uint8(GLib.MAXUINT8), + GLib.MAXUINT8) self.assertEqual(Everything.test_uint8(0), 0) self.assertRaises(OverflowError, Everything.test_uint8, -1) - self.assertRaises(OverflowError, Everything.test_uint8, GObject.G_MAXUINT8 + 1) + self.assertRaises(OverflowError, Everything.test_uint8, GLib.MAXUINT8 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT8 + 1, GLib.MAXUINT8)): + Everything.test_uint8(GLib.MAXUINT8 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXUINT8)): + Everything.test_uint8(GLib.MAXUINT64 * 2) def test_int16(self): - self.assertEqual(Everything.test_int16(GObject.G_MAXINT16), - GObject.G_MAXINT16) - self.assertEqual(Everything.test_int16(GObject.G_MININT16), - GObject.G_MININT16) - self.assertRaises(OverflowError, Everything.test_int16, GObject.G_MAXINT16 + 1) - - self.assertEqual(Everything.test_uint16(GObject.G_MAXUINT16), - GObject.G_MAXUINT16) + self.assertEqual(Everything.test_int16(GLib.MAXINT16), + GLib.MAXINT16) + self.assertEqual(Everything.test_int16(GLib.MININT16), + GLib.MININT16) + + with pytest.raises( + OverflowError, + match="32768 not in range -32768 to 32767"): + Everything.test_int16(GLib.MAXINT16 + 1) + + with pytest.raises( + OverflowError, + match="36893488147419103230 not in range -32768 to 32767"): + Everything.test_int16(GLib.MAXUINT64 * 2) + + def test_uint16(self): + self.assertEqual(Everything.test_uint16(GLib.MAXUINT16), + GLib.MAXUINT16) self.assertEqual(Everything.test_uint16(0), 0) self.assertRaises(OverflowError, Everything.test_uint16, -1) - self.assertRaises(OverflowError, Everything.test_uint16, GObject.G_MAXUINT16 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT16 + 1, GLib.MAXUINT16)): + Everything.test_uint16(GLib.MAXUINT16 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXUINT16)): + Everything.test_uint16(GLib.MAXUINT64 * 2) def test_int32(self): - self.assertEqual(Everything.test_int32(GObject.G_MAXINT32), - GObject.G_MAXINT32) - self.assertEqual(Everything.test_int32(GObject.G_MININT32), - GObject.G_MININT32) - self.assertRaises(OverflowError, Everything.test_int32, GObject.G_MAXINT32 + 1) - - self.assertEqual(Everything.test_uint32(GObject.G_MAXUINT32), - GObject.G_MAXUINT32) + self.assertEqual(Everything.test_int32(GLib.MAXINT32), + GLib.MAXINT32) + self.assertEqual(Everything.test_int32(GLib.MININT32), + GLib.MININT32) + + with pytest.raises( + OverflowError, + match="2147483648 not in range -2147483648 to 2147483647"): + Everything.test_int32(GLib.MAXINT32 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range -2147483648 to 2147483647" % ( + GLib.MAXINT64 + 1,)): + Everything.test_int32(GLib.MAXINT64 + 1) + + def test_uint32(self): + self.assertEqual(Everything.test_uint32(GLib.MAXUINT32), + GLib.MAXUINT32) self.assertEqual(Everything.test_uint32(0), 0) self.assertRaises(OverflowError, Everything.test_uint32, -1) - self.assertRaises(OverflowError, Everything.test_uint32, GObject.G_MAXUINT32 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT32 + 1, GLib.MAXUINT32)): + Everything.test_uint32(GLib.MAXUINT32 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXUINT32)): + Everything.test_uint32(GLib.MAXUINT64 * 2) def test_int64(self): - self.assertEqual(Everything.test_int64(GObject.G_MAXINT64), - GObject.G_MAXINT64) - self.assertEqual(Everything.test_int64(GObject.G_MININT64), - GObject.G_MININT64) - self.assertRaises(OverflowError, Everything.test_int64, GObject.G_MAXINT64 + 1) - - self.assertEqual(Everything.test_uint64(GObject.G_MAXUINT64), - GObject.G_MAXUINT64) + self.assertEqual(Everything.test_int64(GLib.MAXINT64), + GLib.MAXINT64) + self.assertEqual(Everything.test_int64(GLib.MININT64), + GLib.MININT64) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXINT64 + 1, GLib.MININT64, GLib.MAXINT64)): + Everything.test_int64(GLib.MAXINT64 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXUINT64 * 2, GLib.MININT64, GLib.MAXINT64)): + Everything.test_int64(GLib.MAXUINT64 * 2) + + def test_uint64(self): + self.assertEqual(Everything.test_uint64(GLib.MAXUINT64), + GLib.MAXUINT64) self.assertEqual(Everything.test_uint64(0), 0) self.assertRaises(OverflowError, Everything.test_uint64, -1) - self.assertRaises(OverflowError, Everything.test_uint64, GObject.G_MAXUINT64 + 1) + self.assertRaises(OverflowError, Everything.test_uint64, GLib.MAXUINT64 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 + 1, GLib.MAXUINT64)): + Everything.test_uint64(GLib.MAXUINT64 + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXUINT64)): + Everything.test_uint64(GLib.MAXUINT64 * 2) def test_int(self): - self.assertEqual(Everything.test_int(GObject.G_MAXINT), - GObject.G_MAXINT) - self.assertEqual(Everything.test_int(GObject.G_MININT), - GObject.G_MININT) - self.assertRaises(OverflowError, Everything.test_int, GObject.G_MAXINT + 1) - - self.assertEqual(Everything.test_uint(GObject.G_MAXUINT), - GObject.G_MAXUINT) + self.assertEqual(Everything.test_int(GLib.MAXINT), + GLib.MAXINT) + self.assertEqual(Everything.test_int(GLib.MININT), + GLib.MININT) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXINT + 1, GLib.MININT, GLib.MAXINT)): + Everything.test_int(GLib.MAXINT + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXUINT64 * 2, GLib.MININT, GLib.MAXINT)): + Everything.test_int(GLib.MAXUINT64 * 2) + + def test_uint(self): + self.assertEqual(Everything.test_uint(GLib.MAXUINT), + GLib.MAXUINT) self.assertEqual(Everything.test_uint(0), 0) self.assertRaises(OverflowError, Everything.test_uint, -1) - self.assertRaises(OverflowError, Everything.test_uint, GObject.G_MAXUINT + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT + 1, GLib.MAXUINT)): + Everything.test_uint(GLib.MAXUINT + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXUINT)): + Everything.test_uint(GLib.MAXUINT64 * 2) def test_short(self): - self.assertEqual(Everything.test_short(GObject.G_MAXSHORT), - GObject.G_MAXSHORT) - self.assertEqual(Everything.test_short(GObject.G_MINSHORT), - GObject.G_MINSHORT) - self.assertRaises(OverflowError, Everything.test_short, GObject.G_MAXSHORT + 1) - - self.assertEqual(Everything.test_ushort(GObject.G_MAXUSHORT), - GObject.G_MAXUSHORT) + self.assertEqual(Everything.test_short(GLib.MAXSHORT), + GLib.MAXSHORT) + self.assertEqual(Everything.test_short(GLib.MINSHORT), + GLib.MINSHORT) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXSHORT + 1, GLib.MINSHORT, GLib.MAXSHORT)): + Everything.test_short(GLib.MAXSHORT + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXUINT64 * 2, GLib.MINSHORT, GLib.MAXSHORT)): + Everything.test_short(GLib.MAXUINT64 * 2) + + def test_ushort(self): + self.assertEqual(Everything.test_ushort(GLib.MAXUSHORT), + GLib.MAXUSHORT) self.assertEqual(Everything.test_ushort(0), 0) self.assertRaises(OverflowError, Everything.test_ushort, -1) - self.assertRaises(OverflowError, Everything.test_ushort, GObject.G_MAXUSHORT + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUSHORT + 1, GLib.MAXUSHORT)): + Everything.test_ushort(GLib.MAXUSHORT + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXUSHORT)): + Everything.test_ushort(GLib.MAXUINT64 * 2) def test_long(self): - self.assertEqual(Everything.test_long(GObject.G_MAXLONG), - GObject.G_MAXLONG) - self.assertEqual(Everything.test_long(GObject.G_MINLONG), - GObject.G_MINLONG) - self.assertRaises(OverflowError, Everything.test_long, GObject.G_MAXLONG + 1) - - self.assertEqual(Everything.test_ulong(GObject.G_MAXULONG), - GObject.G_MAXULONG) + self.assertEqual(Everything.test_long(GLib.MAXLONG), + GLib.MAXLONG) + self.assertEqual(Everything.test_long(GLib.MINLONG), + GLib.MINLONG) + self.assertRaises(OverflowError, Everything.test_long, GLib.MAXLONG + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXLONG + 1, GLib.MINLONG, GLib.MAXLONG)): + Everything.test_long(GLib.MAXLONG + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXUINT64 * 2, GLib.MINLONG, GLib.MAXLONG)): + Everything.test_long(GLib.MAXUINT64 * 2) + + def test_ulong(self): + self.assertEqual(Everything.test_ulong(GLib.MAXULONG), + GLib.MAXULONG) self.assertEqual(Everything.test_ulong(0), 0) self.assertRaises(OverflowError, Everything.test_ulong, -1) - self.assertRaises(OverflowError, Everything.test_ulong, GObject.G_MAXULONG + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXULONG + 1, GLib.MAXULONG)): + Everything.test_ulong(GLib.MAXULONG + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXULONG)): + Everything.test_ulong(GLib.MAXUINT64 * 2) + + def test_ssize(self): + self.assertEqual(Everything.test_ssize(GLib.MAXSSIZE), + GLib.MAXSSIZE) + self.assertEqual(Everything.test_ssize(GLib.MINSSIZE), + GLib.MINSSIZE) + self.assertRaises(OverflowError, Everything.test_ssize, GLib.MAXSSIZE + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXSSIZE + 1, GLib.MINSSIZE, GLib.MAXSSIZE)): + Everything.test_ssize(GLib.MAXSSIZE + 1) + + with pytest.raises( + OverflowError, + match="%s not in range %s to %s" % ( + GLib.MAXUINT64 * 2, GLib.MINSSIZE, GLib.MAXSSIZE)): + Everything.test_ssize(GLib.MAXUINT64 * 2) def test_size(self): - self.assertEqual(Everything.test_ssize(GObject.G_MAXSSIZE), - GObject.G_MAXSSIZE) - self.assertEqual(Everything.test_ssize(GObject.G_MINSSIZE), - GObject.G_MINSSIZE) - self.assertRaises(OverflowError, Everything.test_ssize, GObject.G_MAXSSIZE + 1) - - self.assertEqual(Everything.test_size(GObject.G_MAXSIZE), - GObject.G_MAXSIZE) + self.assertEqual(Everything.test_size(GLib.MAXSIZE), + GLib.MAXSIZE) self.assertEqual(Everything.test_size(0), 0) self.assertRaises(OverflowError, Everything.test_size, -1) - self.assertRaises(OverflowError, Everything.test_size, GObject.G_MAXSIZE + 1) + self.assertRaises(OverflowError, Everything.test_size, GLib.MAXSIZE + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXSIZE + 1, GLib.MAXSIZE)): + Everything.test_size(GLib.MAXSIZE + 1) + + with pytest.raises( + OverflowError, + match="%s not in range 0 to %s" % ( + GLib.MAXUINT64 * 2, GLib.MAXSIZE)): + Everything.test_size(GLib.MAXUINT64 * 2) def test_timet(self): self.assertEqual(Everything.test_timet(42), 42) - self.assertRaises(OverflowError, Everything.test_timet, GObject.G_MAXUINT64 + 1) + self.assertRaises(OverflowError, Everything.test_timet, GLib.MAXUINT64 + 1) def test_unichar(self): self.assertEqual("c", Everything.test_unichar("c")) + self.assertEqual(chr(sys.maxunicode), Everything.test_unichar(chr(sys.maxunicode))) - if sys.version_info < (3, 0): - self.assertEqual(UNICHAR, Everything.test_unichar(PY2_UNICODE_UNICHAR)) - self.assertEqual(UNICHAR, Everything.test_unichar(UNICHAR)) + self.assertEqual(u"♥", Everything.test_unichar(u"♥")) self.assertRaises(TypeError, Everything.test_unichar, "") self.assertRaises(TypeError, Everything.test_unichar, "morethanonechar") def test_float(self): - self.assertEqual(Everything.test_float(GObject.G_MAXFLOAT), - GObject.G_MAXFLOAT) - self.assertEqual(Everything.test_float(GObject.G_MINFLOAT), - GObject.G_MINFLOAT) - self.assertRaises(OverflowError, Everything.test_float, GObject.G_MAXFLOAT * 2) + self.assertEqual(Everything.test_float(GLib.MAXFLOAT), + GLib.MAXFLOAT) + self.assertEqual(Everything.test_float(GLib.MINFLOAT), + GLib.MINFLOAT) + self.assertRaises(OverflowError, Everything.test_float, GLib.MAXFLOAT * 2) + + with pytest.raises( + OverflowError, + match=re.escape("%s not in range %s to %s" % ( + GLib.MAXFLOAT * 2, -GLib.MAXFLOAT, GLib.MAXFLOAT))): + Everything.test_float(GLib.MAXFLOAT * 2) def test_double(self): - self.assertEqual(Everything.test_double(GObject.G_MAXDOUBLE), - GObject.G_MAXDOUBLE) - self.assertEqual(Everything.test_double(GObject.G_MINDOUBLE), - GObject.G_MINDOUBLE) + self.assertEqual(Everything.test_double(GLib.MAXDOUBLE), + GLib.MAXDOUBLE) + self.assertEqual(Everything.test_double(GLib.MINDOUBLE), + GLib.MINDOUBLE) (two, three) = Everything.test_multi_double_args(2.5) self.assertAlmostEqual(two, 5.0) self.assertAlmostEqual(three, 7.5) def test_value(self): - self.assertEqual(Everything.test_int_value_arg(GObject.G_MAXINT), GObject.G_MAXINT) - self.assertEqual(Everything.test_value_return(GObject.G_MAXINT), GObject.G_MAXINT) + self.assertEqual(Everything.test_int_value_arg(GLib.MAXINT), GLib.MAXINT) + self.assertEqual(Everything.test_value_return(GLib.MAXINT), GLib.MAXINT) def test_variant(self): v = Everything.test_gvariant_i() @@ -254,27 +420,38 @@ class TestEverything(unittest.TestCase): timeout = v.lookup_value('timeout', None) self.assertEqual(timeout.get_int32(), 10) - def test_string(self): - const_str = b'const \xe2\x99\xa5 utf8' - if sys.version_info >= (3, 0): - const_str = const_str.decode('UTF-8') - noconst_str = 'non' + const_str - + def test_utf8_const_return(self): self.assertEqual(Everything.test_utf8_const_return(), const_str) + + def test_utf8_nonconst_return(self): self.assertEqual(Everything.test_utf8_nonconst_return(), noconst_str) + + def test_utf8_out(self): self.assertEqual(Everything.test_utf8_out(), noconst_str) + def test_utf8_const_in(self): Everything.test_utf8_const_in(const_str) + + def test_utf8_inout(self): self.assertEqual(Everything.test_utf8_inout(const_str), noconst_str) - self.assertEqual(Everything.test_filename_return(), ['åäö', '/etc/fstab']) + def test_filename_return(self): + if os.name != "nt": + result = [os.fsdecode(b'\xc3\xa5\xc3\xa4\xc3\xb6'), '/etc/fstab'] + else: + result = ['åäö', '/etc/fstab'] + self.assertEqual(Everything.test_filename_return(), result) + def test_int_out_utf8(self): # returns g_utf8_strlen() in out argument self.assertEqual(Everything.test_int_out_utf8(''), 0) self.assertEqual(Everything.test_int_out_utf8('hello world'), 11) self.assertEqual(Everything.test_int_out_utf8('åäö'), 3) + def test_utf8_out_out(self): self.assertEqual(Everything.test_utf8_out_out(), ('first', 'second')) + + def test_utf8_out_nonconst_return(self): self.assertEqual(Everything.test_utf8_out_nonconst_return(), ('first', 'second')) def test_enum(self): @@ -342,7 +519,7 @@ class TestEverything(unittest.TestCase): Everything.test_int8() except TypeError: (e_type, e) = sys.exc_info()[:2] - self.assertEqual(e.args, ("test_int8() takes exactly 1 argument (0 given)",)) + self.assertEqual(e.args, ("Regress.test_int8() takes exactly 1 argument (0 given)",)) def test_gtypes(self): gchararray_gtype = GObject.type_from_name('gchararray') @@ -389,53 +566,87 @@ class TestEverything(unittest.TestCase): # test that there are no duplicates returned self.assertEqual(len(attr_list), len(set(attr_list))) - def test_array(self): + def test_array_int_in_empty(self): self.assertEqual(Everything.test_array_int_in([]), 0) + + def test_array_int_in(self): self.assertEqual(Everything.test_array_int_in([1, 5, -2]), 4) + + def test_array_int_out(self): self.assertEqual(Everything.test_array_int_out(), [0, 1, 2, 3, 4]) + + def test_array_int_full_out(self): self.assertEqual(Everything.test_array_int_full_out(), [0, 1, 2, 3, 4]) + + def test_array_int_none_out(self): self.assertEqual(Everything.test_array_int_none_out(), [1, 2, 3, 4, 5]) + + def test_array_int_inout(self): self.assertEqual(Everything.test_array_int_inout([1, 5, 42, -8]), [6, 43, -7]) - if sys.version_info >= (3, 0): - self.assertEqual(Everything.test_array_gint8_in(b'\x01\x03\x05'), 9) + def test_array_int_inout_empty(self): + self.assertEqual(Everything.test_array_int_inout([]), []) + + def test_array_gint8_in(self): + self.assertEqual(Everything.test_array_gint8_in(b'\x01\x03\x05'), 9) self.assertEqual(Everything.test_array_gint8_in([1, 3, 5, -50]), -41) + + def test_array_gint16_in(self): self.assertEqual(Everything.test_array_gint16_in([256, 257, -1000, 10000]), 9513) + + def test_array_gint32_in(self): self.assertEqual(Everything.test_array_gint32_in([30000, 1, -2]), 29999) + + def test_array_gint64_in(self): self.assertEqual(Everything.test_array_gint64_in([2 ** 33, 2 ** 34]), 2 ** 33 + 2 ** 34) + def test_array_gtype_in(self): self.assertEqual(Everything.test_array_gtype_in( [GObject.TYPE_STRING, GObject.TYPE_UINT64, GObject.TYPE_VARIANT]), '[gchararray,guint64,GVariant,]') - def test_array_fixed_size(self): + def test_array_fixed_size_int_in(self): # fixed length of 5 self.assertEqual(Everything.test_array_fixed_size_int_in([1, 2, -10, 5, 3]), 1) + + def test_array_fixed_size_int_in_error(self): self.assertRaises(ValueError, Everything.test_array_fixed_size_int_in, [1, 2, 3, 4]) self.assertRaises(ValueError, Everything.test_array_fixed_size_int_in, [1, 2, 3, 4, 5, 6]) + def test_array_fixed_size_int_out(self): self.assertEqual(Everything.test_array_fixed_size_int_out(), [0, 1, 2, 3, 4]) + + def test_array_fixed_size_int_return(self): self.assertEqual(Everything.test_array_fixed_size_int_return(), [0, 1, 2, 3, 4]) - def test_ptrarray(self): - # transfer container + def test_garray_container_return(self): + # GPtrArray transfer container result = Everything.test_garray_container_return() self.assertEqual(result, ['regress']) result = None - # transfer full + def test_garray_full_return(self): + # GPtrArray transfer full result = Everything.test_garray_full_return() self.assertEqual(result, ['regress']) result = None - def test_strv(self): + def test_strv_out(self): self.assertEqual(Everything.test_strv_out(), ['thanks', 'for', 'all', 'the', 'fish']) + + def test_strv_out_c(self): self.assertEqual(Everything.test_strv_out_c(), ['thanks', 'for', 'all', 'the', 'fish']) + + def test_strv_out_container(self): self.assertEqual(Everything.test_strv_out_container(), ['1', '2', '3']) + + def test_strv_outarg(self): self.assertEqual(Everything.test_strv_outarg(), ['1', '2', '3']) + def test_strv_in_gvalue(self): self.assertEqual(Everything.test_strv_in_gvalue(), ['one', 'two', 'three']) + def test_strv_in(self): Everything.test_strv_in(['1', '2', '3']) def test_glist(self): @@ -447,6 +658,12 @@ class TestEverything(unittest.TestCase): Everything.test_glist_nothing_in(['1', '2', '3']) Everything.test_glist_nothing_in2(['1', '2', '3']) + @unittest.skipUnless(hasattr(Everything, 'test_glist_gtype_container_in'), + 'Requires newer version of GI') + def test_glist_gtype(self): + Everything.test_glist_gtype_container_in( + [Everything.TestObj, Everything.TestSubObj]) + def test_gslist(self): self.assertEqual(Everything.test_gslist_nothing_return(), ['1', '2', '3']) self.assertEqual(Everything.test_gslist_nothing_return2(), ['1', '2', '3']) @@ -510,6 +727,7 @@ class TestEverything(unittest.TestCase): Everything.test_ghash_gvalue_in(data) data = None + @unittest.skipIf(platform.python_implementation() == "PyPy", "CPython only") def test_struct_gpointer(self): glist = GLib.List() raw = RawGList.from_wrapped(glist) @@ -540,13 +758,11 @@ class TestEverything(unittest.TestCase): (e_type, e_value, e_tb) = sys.exc_info() self.assertEqual(e_type, TypeError) self.assertTrue('TestBoxedPrivate' in str(e_value), str(e_value)) - self.assertTrue('override' in str(e_value), str(e_value)) self.assertTrue('constructor' in str(e_value), str(e_value)) tb = ''.join(traceback.format_exception(e_type, e_value, e_tb)) - self.assertTrue('tests/test_everything.py", line' in tb, tb) + self.assertTrue('test_everything.py", line' in tb, tb) -@unittest.skipUnless(has_cairo, 'built without cairo support') class TestNullableArgs(unittest.TestCase): def test_in_nullable_hash(self): Everything.test_ghash_null_in(None) @@ -584,7 +800,6 @@ class TestNullableArgs(unittest.TestCase): self.assertEqual(None, Everything.TestObj.null_out()) -@unittest.skipUnless(has_cairo, 'built without cairo support') class TestCallbacks(unittest.TestCase): called = False main_loop = GLib.MainLoop() @@ -610,7 +825,10 @@ class TestCallbacks(unittest.TestCase): # note that we do NOT expect the ZeroDivisionError to be propagated # through from the callback, as it crosses the Python<->C boundary # twice. (See GNOME #616279) - Everything.test_simple_callback(callback) + with capture_exceptions() as exc: + Everything.test_simple_callback(callback) + self.assertTrue(exc) + self.assertEqual(exc[0].type, ZeroDivisionError) def test_double_callback_exception(self): """ @@ -629,7 +847,10 @@ class TestCallbacks(unittest.TestCase): # note that we do NOT expect the ZeroDivisionError to be propagated # through from the callback, as it crosses the Python<->C boundary # twice. (See GNOME #616279) - Everything.test_simple_callback(callback) + with capture_exceptions() as exc: + Everything.test_simple_callback(callback) + self.assertTrue(exc) + self.assertEqual(exc[0].type, ZeroDivisionError) def test_return_value_callback(self): TestCallbacks.called = False @@ -650,14 +871,17 @@ class TestCallbacks(unittest.TestCase): TestCallbacks.called = True return 44 - ud_refcount = sys.getrefcount(ud) - callback_refcount = sys.getrefcount(callback) + if hasattr(sys, "getrefcount"): + ud_refcount = sys.getrefcount(ud) + callback_refcount = sys.getrefcount(callback) self.assertEqual(Everything.test_callback_async(callback, ud), None) # Callback should not have run and the ref count is increased by 1 self.assertEqual(TestCallbacks.called, False) - self.assertEqual(sys.getrefcount(callback), callback_refcount + 1) - self.assertEqual(sys.getrefcount(ud), ud_refcount + 1) + + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount + 1) + self.assertEqual(sys.getrefcount(ud), ud_refcount + 1) # test_callback_thaw_async will run the callback previously supplied. # references should be auto decremented after this call. @@ -665,8 +889,9 @@ class TestCallbacks(unittest.TestCase): self.assertTrue(TestCallbacks.called) # Make sure refcounts are returned to normal - self.assertEqual(sys.getrefcount(callback), callback_refcount) - self.assertEqual(sys.getrefcount(ud), ud_refcount) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount) + self.assertEqual(sys.getrefcount(ud), ud_refcount) def test_callback_scope_call_multi(self): # This tests a callback that gets called multiple times from a @@ -677,12 +902,15 @@ class TestCallbacks(unittest.TestCase): TestCallbacks.called += 1 return TestCallbacks.called - refcount = sys.getrefcount(callback) + if hasattr(sys, "getrefcount"): + refcount = sys.getrefcount(callback) result = Everything.test_multi_callback(callback) # first callback should give 1, second 2, and the function sums them up self.assertEqual(result, 3) self.assertEqual(TestCallbacks.called, 2) - self.assertEqual(sys.getrefcount(callback), refcount) + + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), refcount) def test_callback_scope_call_array(self): # This tests a callback that gets called multiple times from a @@ -695,13 +923,37 @@ class TestCallbacks(unittest.TestCase): TestCallbacks.callargs.append((one, two)) return len(TestCallbacks.callargs) - refcount = sys.getrefcount(callback) + if hasattr(sys, "getrefcount"): + refcount = sys.getrefcount(callback) result = Everything.test_array_callback(callback) # first callback should give 1, second 2, and the function sums them up self.assertEqual(result, 3) self.assertEqual(TestCallbacks.callargs, [([-1, 0, 1, 2], ['one', 'two', 'three'])] * 2) - self.assertEqual(sys.getrefcount(callback), refcount) + + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), refcount) + + @unittest.skipUnless(hasattr(Everything, 'test_array_inout_callback'), + 'Requires newer version of GI') + def test_callback_scope_call_array_inout(self): + # This tests a callback that gets called multiple times from a + # single scope call in python with inout array arguments + TestCallbacks.callargs = [] + + def callback(ints, ints_length): + TestCallbacks.callargs.append(ints) + return ints[1:], len(ints[1:]) + + if hasattr(sys, "getrefcount"): + refcount = sys.getrefcount(callback) + result = Everything.test_array_inout_callback(callback) + self.assertEqual(TestCallbacks.callargs, + [[-2, -1, 0, 1, 2], [-1, 0, 1, 2]]) + # first callback should give 4, second 3 + self.assertEqual(result, 3) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), refcount) def test_callback_userdata(self): TestCallbacks.called = 0 @@ -717,6 +969,99 @@ class TestCallbacks(unittest.TestCase): self.assertEqual(TestCallbacks.called, 100) + def test_callback_userdata_no_user_data(self): + TestCallbacks.called = 0 + + def callback(): + TestCallbacks.called += 1 + return TestCallbacks.called + + for i in range(100): + val = Everything.test_callback_user_data(callback) + self.assertEqual(val, i + 1) + + self.assertEqual(TestCallbacks.called, 100) + + def test_callback_userdata_varargs(self): + TestCallbacks.called = 0 + collected_user_data = [] + + def callback(a, b): + collected_user_data.extend([a, b]) + TestCallbacks.called += 1 + return TestCallbacks.called + + for i in range(10): + val = Everything.test_callback_user_data(callback, 1, 2) + self.assertEqual(val, i + 1) + + self.assertEqual(TestCallbacks.called, 10) + self.assertSequenceEqual(collected_user_data, [1, 2] * 10) + + def test_callback_userdata_as_kwarg_tuple(self): + TestCallbacks.called = 0 + collected_user_data = [] + + def callback(user_data): + collected_user_data.extend(user_data) + TestCallbacks.called += 1 + return TestCallbacks.called + + for i in range(10): + val = Everything.test_callback_user_data(callback, user_data=(1, 2)) + self.assertEqual(val, i + 1) + + self.assertEqual(TestCallbacks.called, 10) + self.assertSequenceEqual(collected_user_data, [1, 2] * 10) + + def test_callback_user_data_middle_none(self): + cb_info = {} + + def callback(userdata): + cb_info['called'] = True + cb_info['userdata'] = userdata + return 1 + + (y, z, q) = Everything.test_torture_signature_2( + 42, callback, None, 'some string', 3) + self.assertEqual(y, 42) + self.assertEqual(z, 84) + self.assertEqual(q, 14) + self.assertTrue(cb_info['called']) + self.assertEqual(cb_info['userdata'], None) + + def test_callback_user_data_middle_single(self): + cb_info = {} + + def callback(userdata): + cb_info['called'] = True + cb_info['userdata'] = userdata + return 1 + + (y, z, q) = Everything.test_torture_signature_2( + 42, callback, 'User Data', 'some string', 3) + self.assertEqual(y, 42) + self.assertEqual(z, 84) + self.assertEqual(q, 14) + self.assertTrue(cb_info['called']) + self.assertEqual(cb_info['userdata'], 'User Data') + + def test_callback_user_data_middle_tuple(self): + cb_info = {} + + def callback(userdata): + cb_info['called'] = True + cb_info['userdata'] = userdata + return 1 + + (y, z, q) = Everything.test_torture_signature_2( + 42, callback, (-5, 'User Data'), 'some string', 3) + self.assertEqual(y, 42) + self.assertEqual(z, 84) + self.assertEqual(q, 14) + self.assertTrue(cb_info['called']) + self.assertEqual(cb_info['userdata'], (-5, 'User Data')) + def test_async_ready_callback(self): TestCallbacks.called = False TestCallbacks.main_loop = GLib.MainLoop() @@ -740,8 +1085,9 @@ class TestCallbacks(unittest.TestCase): TestCallbacks.called += 1 return 33 - value_refcount = sys.getrefcount(ud) - callback_refcount = sys.getrefcount(callback) + if hasattr(sys, "getrefcount"): + value_refcount = sys.getrefcount(ud) + callback_refcount = sys.getrefcount(callback) # Callback is immediately called. for i in range(100): @@ -749,14 +1095,16 @@ class TestCallbacks(unittest.TestCase): self.assertEqual(res, 33) self.assertEqual(TestCallbacks.called, 100) - self.assertEqual(sys.getrefcount(callback), callback_refcount + 100) - self.assertEqual(sys.getrefcount(ud), value_refcount + 100) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount + 100) + self.assertEqual(sys.getrefcount(ud), value_refcount + 100) # thaw will call the callback again, this time resources should be freed self.assertEqual(Everything.test_callback_thaw_notifications(), 33 * 100) self.assertEqual(TestCallbacks.called, 200) - self.assertEqual(sys.getrefcount(callback), callback_refcount) - self.assertEqual(sys.getrefcount(ud), value_refcount) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount) + self.assertEqual(sys.getrefcount(ud), value_refcount) def test_callback_scope_notified_with_destroy_no_user_data(self): TestCallbacks.called = 0 @@ -766,7 +1114,8 @@ class TestCallbacks(unittest.TestCase): TestCallbacks.called += 1 return 34 - callback_refcount = sys.getrefcount(callback) + if hasattr(sys, "getrefcount"): + callback_refcount = sys.getrefcount(callback) # Run with warning as exception with warnings.catch_warnings(record=True) as w: @@ -776,7 +1125,8 @@ class TestCallbacks(unittest.TestCase): callback) self.assertEqual(TestCallbacks.called, 0) - self.assertEqual(sys.getrefcount(callback), callback_refcount) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount) # Run with warning as warning with warnings.catch_warnings(record=True) as w: @@ -791,13 +1141,15 @@ class TestCallbacks(unittest.TestCase): self.assertEqual(res, 34) self.assertEqual(TestCallbacks.called, 1) - self.assertEqual(sys.getrefcount(callback), callback_refcount + 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount + 1) # thaw will call the callback again, # refcount will not go down without user_data parameter self.assertEqual(Everything.test_callback_thaw_notifications(), 34) self.assertEqual(TestCallbacks.called, 2) - self.assertEqual(sys.getrefcount(callback), callback_refcount + 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(callback), callback_refcount + 1) def test_callback_in_methods(self): object_ = Everything.TestObj() @@ -875,7 +1227,6 @@ class TestCallbacks(unittest.TestCase): self.assertEqual(mydict, {'foo': 1, 'bar': 2, 'new': 42}) -@unittest.skipUnless(has_cairo, 'built without cairo support') class TestClosures(unittest.TestCase): def test_no_arg(self): def callback(): @@ -924,65 +1275,19 @@ class TestClosures(unittest.TestCase): def callback(variant): return 'no_variant' - # reset last error - sys.last_type = None - - # this does not directly raise an exception (see - # https://bugzilla.gnome.org/show_bug.cgi?id=616279) - result = Everything.test_closure_variant(callback, GLib.Variant('i', 42)) + with capture_exceptions() as exc: + # this does not directly raise an exception (see + # https://bugzilla.gnome.org/show_bug.cgi?id=616279) + result = Everything.test_closure_variant(callback, GLib.Variant('i', 42)) # ... but the result shouldn't be a string self.assertEqual(result, None) # and the error should be shown - self.assertEqual(sys.last_type, TypeError) - self.assertTrue('return value' in str(sys.last_value), sys.last_value) - - -@unittest.skipUnless(has_cairo, 'built without cairo support') -class TestProperties(unittest.TestCase): - - def test_basic(self): - object_ = Everything.TestObj() + self.assertEqual(len(exc), 1) + self.assertEqual(exc[0].type, TypeError) + self.assertTrue('return value' in str(exc[0].value), exc[0].value) - self.assertEqual(object_.props.int, 0) - object_.props.int = 42 - self.assertTrue(isinstance(object_.props.int, int)) - self.assertEqual(object_.props.int, 42) - - self.assertEqual(object_.props.float, 0.0) - object_.props.float = 42.42 - self.assertTrue(isinstance(object_.props.float, float)) - self.assertAlmostEqual(object_.props.float, 42.42, places=5) - - self.assertEqual(object_.props.double, 0.0) - object_.props.double = 42.42 - self.assertTrue(isinstance(object_.props.double, float)) - self.assertAlmostEqual(object_.props.double, 42.42, places=5) - - self.assertEqual(object_.props.string, None) - object_.props.string = 'mec' - self.assertTrue(isinstance(object_.props.string, str)) - self.assertEqual(object_.props.string, 'mec') - - self.assertEqual(object_.props.gtype, GObject.TYPE_INVALID) - object_.props.gtype = int - self.assertEqual(object_.props.gtype, GObject.TYPE_INT) - - def test_hash_table(self): - object_ = Everything.TestObj() - self.assertEqual(object_.props.hash_table, None) - - object_.props.hash_table = {'mec': 56} - self.assertTrue(isinstance(object_.props.hash_table, dict)) - self.assertEqual(list(object_.props.hash_table.items())[0], ('mec', 56)) - - def test_list(self): - object_ = Everything.TestObj() - self.assertEqual(object_.props.list, []) - - object_.props.list = ['1', '2', '3'] - self.assertTrue(isinstance(object_.props.list, list)) - self.assertEqual(object_.props.list, ['1', '2', '3']) +class TestBoxed(unittest.TestCase): def test_boxed(self): object_ = Everything.TestObj() self.assertEqual(object_.props.boxed, None) @@ -1014,6 +1319,15 @@ class TestProperties(unittest.TestCase): self.assertTrue(boxed42_2.equals(boxed42)) self.assertTrue(boxed42.equals(boxed42)) + def test_boxed_b_constructor(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + boxed = Everything.TestBoxedB(42, 47) + self.assertTrue(issubclass(warn[0].category, DeprecationWarning)) + + self.assertEqual(boxed.some_int8, 0) + self.assertEqual(boxed.some_long, 0) + def test_boxed_c_equality(self): boxed = Everything.TestBoxedC() # TestBoxedC uses refcounting, so we know that @@ -1022,71 +1336,111 @@ class TestProperties(unittest.TestCase): self.assertEqual(boxed, copy) self.assertNotEqual(id(boxed), id(copy)) - def test_gtype(self): - object_ = Everything.TestObj() - self.assertEqual(object_.props.gtype, GObject.TYPE_INVALID) - object_.props.gtype = int - self.assertEqual(object_.props.gtype, GObject.TYPE_INT) - - object_ = Everything.TestObj(gtype=int) - self.assertEqual(object_.props.gtype, GObject.TYPE_INT) - object_.props.gtype = str - self.assertEqual(object_.props.gtype, GObject.TYPE_STRING) - - def test_parent_class(self): - class A(Everything.TestObj): - prop1 = GObject.Property(type=int) + def test_boxed_c_wrapper(self): + wrapper = Everything.TestBoxedCWrapper() + obj = wrapper.get() - a = A() - a.props.int = 20 - self.assertEqual(a.props.int, 20) + # TestBoxedC uses refcounting, so we know that + # it should be 2 at this point: + # - one owned by @wrapper + # - another owned by @obj + self.assertEqual(obj.refcount, 2) + del wrapper + gc.collect() + gc.collect() + self.assertEqual(obj.refcount, 1) + + def test_boxed_c_wrapper_copy(self): + wrapper = Everything.TestBoxedCWrapper() + wrapper_copy = wrapper.copy() + obj = wrapper.get() - # test parent property which needs introspection - a.props.list = ("str1", "str2") - self.assertEqual(a.props.list, ["str1", "str2"]) + # TestBoxedC uses refcounting, so we know that + # it should be 3 at this point: + # - one owned by @wrapper + # - one owned by @wrapper_copy + # - another owned by @obj + self.assertEqual(obj.refcount, 3) + del wrapper + gc.collect() + gc.collect() + self.assertEqual(obj.refcount, 2) + del wrapper_copy + gc.collect() + gc.collect() + self.assertEqual(obj.refcount, 1) + del obj + gc.collect() + gc.collect() + + def test_array_fixed_boxed_none_out(self): + arr = Everything.test_array_fixed_boxed_none_out() + assert len(arr) == 2 + assert arr[0].refcount == 2 + assert arr[1].refcount == 2 + + def test_gvalue_out_boxed(self): + # As corruption is random data, check several times. + for i in range(10): + int8 = random.randint(GLib.MININT8, GLib.MAXINT8) + assert Everything.test_gvalue_out_boxed(int8).some_int8 == int8 + + def test_glist_boxed_none_return(self): + assert len(Everything.test_glist_boxed_none_return(0)) == 0 + + list_ = Everything.test_glist_boxed_none_return(2) + assert len(list_) == 2 + assert list_[0].refcount == 2 + assert list_[1].refcount == 2 + + def test_glist_boxed_full_return(self): + assert len(Everything.test_glist_boxed_full_return(0)) == 0 + + list_ = Everything.test_glist_boxed_full_return(2) + assert len(list_) == 2 + assert list_[0].refcount == 1 + assert list_[1].refcount == 1 -@unittest.skipUnless(has_cairo, 'built without cairo support') class TestTortureProfile(unittest.TestCase): def test_torture_profile(self): - import time total_time = 0 print("") object_ = Everything.TestObj() sys.stdout.write("\ttorture test 1 (10000 iterations): ") - start_time = time.clock() + start_time = timeit.default_timer() for i in range(10000): (y, z, q) = object_.torture_signature_0(5000, "Torture Test 1", 12345) - end_time = time.clock() + end_time = timeit.default_timer() delta_time = end_time - start_time total_time += delta_time print("%f secs" % delta_time) sys.stdout.write("\ttorture test 2 (10000 iterations): ") - start_time = time.clock() + start_time = timeit.default_timer() for i in range(10000): (y, z, q) = Everything.TestObj().torture_signature_0( 5000, "Torture Test 2", 12345) - end_time = time.clock() + end_time = timeit.default_timer() delta_time = end_time - start_time total_time += delta_time print("%f secs" % delta_time) sys.stdout.write("\ttorture test 3 (10000 iterations): ") - start_time = time.clock() + start_time = timeit.default_timer() for i in range(10000): try: (y, z, q) = object_.torture_signature_1( 5000, "Torture Test 3", 12345) except: pass - end_time = time.clock() + end_time = timeit.default_timer() delta_time = end_time - start_time total_time += delta_time print("%f secs" % delta_time) @@ -1094,14 +1448,14 @@ class TestTortureProfile(unittest.TestCase): sys.stdout.write("\ttorture test 4 (10000 iterations): ") def callback(userdata): - pass + return 0 userdata = [1, 2, 3, 4] - start_time = time.clock() + start_time = timeit.default_timer() for i in range(10000): (y, z, q) = Everything.test_torture_signature_2( 5000, callback, userdata, "Torture Test 4", 12345) - end_time = time.clock() + end_time = timeit.default_timer() delta_time = end_time - start_time total_time += delta_time print("%f secs" % delta_time) @@ -1109,7 +1463,6 @@ class TestTortureProfile(unittest.TestCase): print("\tTotal: %f sec" % total_time) -@unittest.skipUnless(has_cairo, 'built without cairo support') class TestAdvancedInterfaces(unittest.TestCase): def test_array_objs(self): obj1, obj2 = Everything.test_array_fixed_out_objects() @@ -1132,135 +1485,3 @@ class TestAdvancedInterfaces(unittest.TestCase): ret = obj.skip_return_val_no_out(1) self.assertEqual(ret, None) - - -@unittest.skipUnless(has_cairo, 'built without cairo support') -class TestSignals(unittest.TestCase): - def test_object_param_signal(self): - obj = Everything.TestObj() - - def callback(obj, obj_param): - self.assertEqual(obj_param.props.int, 3) - self.assertGreater(obj_param.__grefcount__, 1) - obj.called = True - - obj.called = False - obj.connect('sig-with-obj', callback) - obj.emit_sig_with_obj() - self.assertTrue(obj.called) - - def test_connect_after(self): - obj = Everything.TestObj() - - def callback(obj, obj_param): - obj.called = True - - obj.called = False - obj.connect_after('sig-with-obj', callback) - obj.emit_sig_with_obj() - self.assertTrue(obj.called) - - def test_connect_object(self): - obj = Everything.TestObj() - - def callback(obj, obj_param): - obj.called = True - - obj.called = False - obj.connect_object('sig-with-obj', callback, obj) - obj.emit_sig_with_obj() - self.assertTrue(obj.called) - - def test_connect_object_after(self): - obj = Everything.TestObj() - - def callback(obj, obj_param): - obj.called = True - - obj.called = False - obj.connect_object_after('sig-with-obj', callback, obj) - obj.emit_sig_with_obj() - self.assertTrue(obj.called) - - def test_int64_param_from_py(self): - obj = Everything.TestObj() - - def callback(obj, i): - obj.callback_i = i - return i - - obj.callback_i = None - obj.connect('sig-with-int64-prop', callback) - rv = obj.emit('sig-with-int64-prop', GObject.G_MAXINT64) - self.assertEqual(rv, GObject.G_MAXINT64) - self.assertEqual(obj.callback_i, GObject.G_MAXINT64) - - def test_uint64_param_from_py(self): - obj = Everything.TestObj() - - def callback(obj, i): - obj.callback_i = i - return i - - obj.callback_i = None - obj.connect('sig-with-uint64-prop', callback) - rv = obj.emit('sig-with-uint64-prop', GObject.G_MAXUINT64) - self.assertEqual(rv, GObject.G_MAXUINT64) - self.assertEqual(obj.callback_i, GObject.G_MAXUINT64) - - def test_int64_param_from_c(self): - obj = Everything.TestObj() - - def callback(obj, i): - obj.callback_i = i - return i - - obj.callback_i = None - - obj.connect('sig-with-int64-prop', callback) - obj.emit_sig_with_int64() - self.assertEqual(obj.callback_i, GObject.G_MAXINT64) - - def test_uint64_param_from_c(self): - obj = Everything.TestObj() - - def callback(obj, i): - obj.callback_i = i - return i - - obj.callback_i = None - - obj.connect('sig-with-uint64-prop', callback) - obj.emit_sig_with_uint64() - self.assertEqual(obj.callback_i, GObject.G_MAXUINT64) - - def test_intarray_ret(self): - obj = Everything.TestObj() - - def callback(obj, i): - obj.callback_i = i - return [i, i + 1] - - obj.callback_i = None - - try: - obj.connect('sig-with-intarray-ret', callback) - except TypeError as e: - # compat with g-i 1.34.x - if 'unknown signal' in str(e): - return - raise - - rv = obj.emit('sig-with-intarray-ret', 42) - self.assertEqual(obj.callback_i, 42) - self.assertEqual(type(rv), GLib.Array) - self.assertEqual(rv.len, 2) - - -@unittest.skipUnless(has_cairo, 'built without cairo support') -@unittest.skipUnless(Gtk, 'Gtk not available') -class TestPango(unittest.TestCase): - def test_cairo_font_options(self): - screen = Gtk.Window().get_screen() - font_opts = screen.get_font_options() - self.assertEqual(type(font_opts.get_subpixel_order()), int) diff --git a/tests/test_fields.py b/tests/test_fields.py new file mode 100644 index 0000000..b9a06e2 --- /dev/null +++ b/tests/test_fields.py @@ -0,0 +1,185 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- + +import math +import unittest + +from gi.repository import GLib +from gi.repository import Regress +from gi.repository import GIMarshallingTests + + +class Number(object): + + def __init__(self, value): + self.value = value + + def __int__(self): + return int(self.value) + + def __float__(self): + return float(self.value) + + +class TestFields(unittest.TestCase): + + def test_int8(self): + s = Regress.TestStructA() + s.some_int8 = 21 + self.assertEqual(s.some_int8, 21) + + s.some_int8 = b"\x42" + self.assertEqual(s.some_int8, 0x42) + + self.assertRaises(TypeError, setattr, s, "some_int8", b"ab") + self.assertRaises(TypeError, setattr, s, "some_int8", None) + self.assertRaises(OverflowError, setattr, s, "some_int8", 128) + self.assertRaises(OverflowError, setattr, s, "some_int8", -129) + + s.some_int8 = 3.6 + self.assertEqual(s.some_int8, 3) + + s.some_int8 = Number(55) + self.assertEqual(s.some_int8, 55) + + def test_int(self): + s = Regress.TestStructA() + s.some_int = GLib.MAXINT + self.assertEqual(s.some_int, GLib.MAXINT) + + self.assertRaises(TypeError, setattr, s, "some_int", b"a") + self.assertRaises(TypeError, setattr, s, "some_int", None) + self.assertRaises( + OverflowError, setattr, s, "some_int", GLib.MAXINT + 1) + self.assertRaises( + OverflowError, setattr, s, "some_int", GLib.MININT - 1) + + s.some_int = 3.6 + self.assertEqual(s.some_int, 3) + + s.some_int = Number(GLib.MININT) + self.assertEqual(s.some_int, GLib.MININT) + + def test_long(self): + s = GIMarshallingTests.SimpleStruct() + s.long_ = GLib.MAXLONG + self.assertEqual(s.long_, GLib.MAXLONG) + + self.assertRaises(TypeError, setattr, s, "long_", b"a") + self.assertRaises(TypeError, setattr, s, "long_", None) + self.assertRaises(OverflowError, setattr, s, "long_", GLib.MAXLONG + 1) + self.assertRaises(OverflowError, setattr, s, "long_", GLib.MINLONG - 1) + + s.long_ = 3.6 + self.assertEqual(s.long_, 3) + + s.long_ = Number(GLib.MINLONG) + self.assertEqual(s.long_, GLib.MINLONG) + + def test_double(self): + s = Regress.TestStructA() + s.some_double = GLib.MAXDOUBLE + self.assertEqual(s.some_double, GLib.MAXDOUBLE) + s.some_double = GLib.MINDOUBLE + self.assertEqual(s.some_double, GLib.MINDOUBLE) + + s.some_double = float("nan") + self.assertTrue(math.isnan(s.some_double)) + + self.assertRaises(TypeError, setattr, s, "some_double", b"a") + self.assertRaises(TypeError, setattr, s, "some_double", None) + + def test_gtype(self): + s = Regress.TestStructE() + + s.some_type = Regress.TestObj + self.assertEqual(s.some_type, Regress.TestObj.__gtype__) + + self.assertRaises(TypeError, setattr, s, "some_type", 42) + + def test_unichar(self): + # I can't find a unichar field.. + pass + + def test_utf8(self): + s = GIMarshallingTests.BoxedStruct() + s.string_ = "hello" + self.assertEqual(s.string_, "hello") + + s.string_ = u"hello" + self.assertEqual(s.string_, u"hello") + + s.string_ = None + self.assertEqual(s.string_, None) + + self.assertRaises(TypeError, setattr, s, "string_", 42) + + def test_array_of_structs(self): + s = Regress.TestStructD() + self.assertEqual(s.array1, []) + self.assertEqual(s.array2, []) + + def test_interface(self): + s = Regress.TestStructC() + + obj = Regress.TestObj() + s.obj = obj + self.assertTrue(s.obj is obj) + + s.obj = None + self.assertTrue(s.obj is None) + + self.assertRaises(TypeError, setattr, s, "obj", object()) + + def test_glist(self): + s = Regress.TestStructD() + self.assertEqual(s.list, []) + + self.assertRaises(TypeError, setattr, s, "list", [object()]) + + def test_gpointer(self): + glist = GLib.List() + + glist.data = 123 + self.assertEqual(glist.data, 123) + + glist.data = None + self.assertEqual(glist.data, 0) + + def test_gptrarray(self): + s = Regress.TestStructD() + self.assertEqual(s.garray, []) + + self.assertRaises(TypeError, setattr, s, "garray", [object()]) + + def test_enum(self): + s = Regress.TestStructA() + + s.some_enum = Regress.TestEnum.VALUE3 + self.assertEqual(s.some_enum, Regress.TestEnum.VALUE3) + + self.assertRaises(TypeError, setattr, s, "some_enum", object()) + + s.some_enum = 0 + self.assertEqual(s.some_enum, Regress.TestEnum.VALUE1) + + def test_union(self): + s = Regress.TestStructE() + self.assertEqual(s.some_union, [None, None]) + + def test_struct(self): + s = GIMarshallingTests.NestedStruct() + + # FIXME: segfaults + # https://bugzilla.gnome.org/show_bug.cgi?id=747002 + # s.simple_struct = None + + self.assertRaises(TypeError, setattr, s, "simple_struct", object()) + + sub = GIMarshallingTests.SimpleStruct() + sub.long_ = 42 + s.simple_struct = sub + self.assertEqual(s.simple_struct.long_, 42) + + def test_ghashtable(self): + obj = Regress.TestObj() + self.assertTrue(obj.hash_table is None) diff --git a/tests/test_gdbus.py b/tests/test_gdbus.py index 805633a..6c9afb0 100644 --- a/tests/test_gdbus.py +++ b/tests/test_gdbus.py @@ -7,6 +7,44 @@ from gi.repository import GLib from gi.repository import Gio +try: + Gio.bus_get_sync(Gio.BusType.SESSION, None) +except GLib.Error: + has_dbus = False +else: + has_dbus = True + + +class TestDBusNodeInfo(unittest.TestCase): + + def test_new_for_xml(self): + info = Gio.DBusNodeInfo.new_for_xml(""" +<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' + 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'> +<node> + <interface name='org.freedesktop.DBus.Introspectable'> + <method name='Introspect'> + <arg name='data' direction='out' type='s'/> + </method> + </interface> +</node> +""") + + interfaces = info.interfaces + del info + assert len(interfaces) == 1 + assert interfaces[0].name == "org.freedesktop.DBus.Introspectable" + methods = interfaces[0].methods + del interfaces + assert len(methods) == 1 + assert methods[0].name == "Introspect" + out_args = methods[0].out_args + assert len(out_args) + del methods + assert out_args[0].name == "data" + + +@unittest.skipUnless(has_dbus, "no dbus running") class TestGDBusClient(unittest.TestCase): def setUp(self): self.bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) @@ -208,3 +246,10 @@ class TestGDBusClient(unittest.TestCase): self.assertTrue(isinstance(data['error'], Exception)) self.assertTrue('InvalidArgs' in str(data['error']), str(data['error'])) + + def test_instantiate_custom_proxy(self): + class SomeProxy(Gio.DBusProxy): + def __init__(self): + Gio.DBusProxy.__init__(self) + + SomeProxy() diff --git a/tests/test_generictreemodel.py b/tests/test_generictreemodel.py index 9fa89ff..ab31579 100644 --- a/tests/test_generictreemodel.py +++ b/tests/test_generictreemodel.py @@ -15,22 +15,26 @@ # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - +# License along with this library; if not, see <http://www.gnu.org/licenses/>. # system import gc import sys import weakref import unittest +import platform # pygobject from gi.repository import GObject -from gi.repository import Gtk -from pygtkcompat.generictreemodel import GenericTreeModel -from pygtkcompat.generictreemodel import _get_user_data_as_pyobject + +try: + from gi.repository import Gtk + from pygtkcompat.generictreemodel import GenericTreeModel + from pygtkcompat.generictreemodel import _get_user_data_as_pyobject + has_gtk = True +except ImportError: + GenericTreeModel = object + has_gtk = False class Node(object): @@ -51,9 +55,9 @@ class Node(object): return 'Node("%s", %s)' % (self.name, self.value) -class TesterModel(GenericTreeModel): +class ATesterModel(GenericTreeModel): def __init__(self): - super(TesterModel, self).__init__() + super(ATesterModel, self).__init__() self.root = Node('root', 0, Node('spam', 1, Node('sushi', 2), @@ -127,10 +131,12 @@ class TesterModel(GenericTreeModel): return child.parent() +@unittest.skipUnless(has_gtk, 'Gtk not available') class TestReferences(unittest.TestCase): def setUp(self): pass + @unittest.skipIf(platform.python_implementation() == "PyPy", "not with PyPy") def test_c_tree_iter_user_data_as_pyobject(self): obj = object() obj_id = id(obj) @@ -145,27 +151,31 @@ class TestReferences(unittest.TestCase): self.assertEqual(sys.getrefcount(obj), ref_count + 1) def test_leak_references_on(self): - model = TesterModel() + model = ATesterModel() obj_ref = weakref.ref(model.root) # Initial refcount is 1 for model.root + the temporary - self.assertEqual(sys.getrefcount(model.root), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 2) # Iter increases by 1 do to assignment to iter.user_data res, it = model.do_get_iter([0]) self.assertEqual(id(model.root), it.user_data) - self.assertEqual(sys.getrefcount(model.root), 3) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 3) # Verify getting a TreeIter more then once does not further increase # the ref count. res2, it2 = model.do_get_iter([0]) self.assertEqual(id(model.root), it2.user_data) - self.assertEqual(sys.getrefcount(model.root), 3) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 3) # Deleting the iter does not decrease refcount because references # leak by default (they are stored in the held_refs pool) del it gc.collect() - self.assertEqual(sys.getrefcount(model.root), 3) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 3) # Deleting a model should free all held references to user data # stored by TreeIters @@ -174,20 +184,23 @@ class TestReferences(unittest.TestCase): self.assertEqual(obj_ref(), None) def test_row_deleted_frees_refs(self): - model = TesterModel() + model = ATesterModel() obj_ref = weakref.ref(model.root) # Initial refcount is 1 for model.root + the temporary - self.assertEqual(sys.getrefcount(model.root), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 2) # Iter increases by 1 do to assignment to iter.user_data res, it = model.do_get_iter([0]) self.assertEqual(id(model.root), it.user_data) - self.assertEqual(sys.getrefcount(model.root), 3) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 3) # Notifying the underlying model of a row_deleted should decrease the # ref count. model.row_deleted(Gtk.TreePath('0'), model.root) - self.assertEqual(sys.getrefcount(model.root), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 2) # Finally deleting the actual object should collect it completely del model.root @@ -195,23 +208,26 @@ class TestReferences(unittest.TestCase): self.assertEqual(obj_ref(), None) def test_leak_references_off(self): - model = TesterModel() + model = ATesterModel() model.leak_references = False obj_ref = weakref.ref(model.root) # Initial refcount is 1 for model.root + the temporary - self.assertEqual(sys.getrefcount(model.root), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 2) # Iter does not increas count by 1 when leak_references is false res, it = model.do_get_iter([0]) self.assertEqual(id(model.root), it.user_data) - self.assertEqual(sys.getrefcount(model.root), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 2) # Deleting the iter does not decrease refcount because assigning user_data # eats references and does not release them. del it gc.collect() - self.assertEqual(sys.getrefcount(model.root), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(model.root), 2) # Deleting the model decreases the final ref, and the object is collected del model @@ -221,7 +237,7 @@ class TestReferences(unittest.TestCase): def test_iteration_refs(self): # Pull iterators off the model using the wrapped C API which will # then call back into the python overrides. - model = TesterModel() + model = ATesterModel() nodes = [node for node in model.iter_depth_first()] values = [node.value for node in nodes] @@ -236,14 +252,16 @@ class TestReferences(unittest.TestCase): # 4 - ref held by the root/children graph itself # 5 - ref held by the model "held_refs" instance var for node in nodes: - self.assertEqual(sys.getrefcount(node), 5) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(node), 5) # A second iteration and storage of the nodes in a new list # should only increase refcounts by 1 even though new # iterators are created and assigned. nodes2 = [node for node in model.iter_depth_first()] for node in nodes2: - self.assertEqual(sys.getrefcount(node), 6) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(node), 6) # Hold weak refs and start verifying ref collection. node_refs = [weakref.ref(node) for node in nodes] @@ -252,14 +270,16 @@ class TestReferences(unittest.TestCase): del nodes2 gc.collect() for node in nodes: - self.assertEqual(sys.getrefcount(node), 5) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(node), 5) # Second round of collection, no more local lists of nodes. del nodes gc.collect() for ref in node_refs: node = ref() - self.assertEqual(sys.getrefcount(node), 4) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(node), 4) # Using invalidate_iters or row_deleted(path, node) will clear out # the pooled refs held internal to the GenericTreeModel implementation. @@ -268,7 +288,8 @@ class TestReferences(unittest.TestCase): gc.collect() for ref in node_refs: node = ref() - self.assertEqual(sys.getrefcount(node), 3) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(node), 3) # Deleting the root node at this point should allow all nodes to be collected # as there is no longer a way to reach the children @@ -279,9 +300,10 @@ class TestReferences(unittest.TestCase): self.assertEqual(ref(), None) +@unittest.skipUnless(has_gtk, 'Gtk not available') class TestIteration(unittest.TestCase): def test_iter_next_root(self): - model = TesterModel() + model = ATesterModel() it = model.get_iter([0]) self.assertEqual(it.user_data, id(model.root)) self.assertEqual(model.root.next, None) @@ -290,7 +312,7 @@ class TestIteration(unittest.TestCase): self.assertEqual(it, None) def test_iter_next_multiple(self): - model = TesterModel() + model = ATesterModel() it = model.get_iter([0, 0]) self.assertEqual(it.user_data, id(model.root.children[0])) @@ -306,6 +328,7 @@ class ErrorModel(GenericTreeModel): pass +@unittest.skipUnless(has_gtk, 'Gtk not available') class ExceptHook(object): """ Temporarily installs an exception hook in a context which @@ -337,6 +360,7 @@ class ExceptHook(object): assert issubclass(got, expected), error_message +@unittest.skipUnless(has_gtk, 'Gtk not available') class TestReturnsAfterError(unittest.TestCase): def setUp(self): self.model = ErrorModel() @@ -352,7 +376,7 @@ class TestReturnsAfterError(unittest.TestCase): self.assertEqual(count, 0) def test_get_column_type(self): - with ExceptHook(NotImplementedError, TypeError): + with ExceptHook(NotImplementedError, ValueError): col_type = self.model.get_column_type(0) self.assertEqual(col_type, GObject.TYPE_INVALID) @@ -406,6 +430,3 @@ class TestReturnsAfterError(unittest.TestCase): with ExceptHook(NotImplementedError): res = self.model.iter_parent(child) self.assertEqual(res, None) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_gi.py b/tests/test_gi.py index c47ac57..261d8b2 100644 --- a/tests/test_gi.py +++ b/tests/test_gi.py @@ -1,5 +1,4 @@ # -*- Mode: Python; py-indent-offset: 4 -*- -# coding=utf-8 # vim: tabstop=4 shiftwidth=4 expandtab import sys @@ -8,29 +7,25 @@ import unittest import tempfile import shutil import os -import locale -import subprocess import gc import weakref import warnings -from io import StringIO, BytesIO +import pickle +import platform import gi +import gi.overrides +from gi import PyGIWarning +from gi import PyGIDeprecationWarning from gi.repository import GObject, GLib, Gio - from gi.repository import GIMarshallingTests +import pytest -from compathelper import _bytes, _unicode +from .helper import capture_exceptions, capture_output -if sys.version_info < (3, 0): - CONSTANT_UTF8 = "const \xe2\x99\xa5 utf8" - PY2_UNICODE_UTF8 = unicode(CONSTANT_UTF8, 'UTF-8') - CHAR_255 = '\xff' -else: - CONSTANT_UTF8 = "const ♥ utf8" - CHAR_255 = bytes([255]) -CONSTANT_NUMBER = 42 +CONSTANT_UTF8 = "const ♥ utf8" +CONSTANT_UCS4 = u"const ♥ utf8" class Number(object): @@ -59,12 +54,11 @@ class Sequence(object): class TestConstant(unittest.TestCase): -# Blocked by https://bugzilla.gnome.org/show_bug.cgi?id=595773 -# def test_constant_utf8(self): -# self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.CONSTANT_UTF8) + def test_constant_utf8(self): + self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.CONSTANT_UTF8) def test_constant_number(self): - self.assertEqual(CONSTANT_NUMBER, GIMarshallingTests.CONSTANT_NUMBER) + self.assertEqual(42, GIMarshallingTests.CONSTANT_NUMBER) def test_min_max_int(self): self.assertEqual(GLib.MAXINT32, 2 ** 31 - 1) @@ -89,6 +83,11 @@ class TestBoolean(unittest.TestCase): GIMarshallingTests.boolean_in_true(1) GIMarshallingTests.boolean_in_false(0) + def test_boolean_in_other_types(self): + GIMarshallingTests.boolean_in_true([""]) + GIMarshallingTests.boolean_in_false([]) + GIMarshallingTests.boolean_in_false(None) + def test_boolean_out(self): self.assertEqual(True, GIMarshallingTests.boolean_out_true()) self.assertEqual(False, GIMarshallingTests.boolean_out_false()) @@ -100,8 +99,8 @@ class TestBoolean(unittest.TestCase): class TestInt8(unittest.TestCase): - MAX = GObject.G_MAXINT8 - MIN = GObject.G_MININT8 + MAX = GLib.MAXINT8 + MIN = GLib.MININT8 def test_int8_return(self): self.assertEqual(self.MAX, GIMarshallingTests.int8_return_max()) @@ -133,7 +132,7 @@ class TestInt8(unittest.TestCase): class TestUInt8(unittest.TestCase): - MAX = GObject.G_MAXUINT8 + MAX = GLib.MAXUINT8 def test_uint8_return(self): self.assertEqual(self.MAX, GIMarshallingTests.uint8_return()) @@ -142,7 +141,7 @@ class TestUInt8(unittest.TestCase): number = Number(self.MAX) GIMarshallingTests.uint8_in(number) - GIMarshallingTests.uint8_in(CHAR_255) + GIMarshallingTests.uint8_in(b'\xff') number.value += 1 self.assertRaises(OverflowError, GIMarshallingTests.uint8_in, number) @@ -159,8 +158,8 @@ class TestUInt8(unittest.TestCase): class TestInt16(unittest.TestCase): - MAX = GObject.G_MAXINT16 - MIN = GObject.G_MININT16 + MAX = GLib.MAXINT16 + MIN = GLib.MININT16 def test_int16_return(self): self.assertEqual(self.MAX, GIMarshallingTests.int16_return_max()) @@ -192,7 +191,7 @@ class TestInt16(unittest.TestCase): class TestUInt16(unittest.TestCase): - MAX = GObject.G_MAXUINT16 + MAX = GLib.MAXUINT16 def test_uint16_return(self): self.assertEqual(self.MAX, GIMarshallingTests.uint16_return()) @@ -218,8 +217,8 @@ class TestUInt16(unittest.TestCase): class TestInt32(unittest.TestCase): - MAX = GObject.G_MAXINT32 - MIN = GObject.G_MININT32 + MAX = GLib.MAXINT32 + MIN = GLib.MININT32 def test_int32_return(self): self.assertEqual(self.MAX, GIMarshallingTests.int32_return_max()) @@ -251,7 +250,7 @@ class TestInt32(unittest.TestCase): class TestUInt32(unittest.TestCase): - MAX = GObject.G_MAXUINT32 + MAX = GLib.MAXUINT32 def test_uint32_return(self): self.assertEqual(self.MAX, GIMarshallingTests.uint32_return()) @@ -336,8 +335,8 @@ class TestUInt64(unittest.TestCase): class TestShort(unittest.TestCase): - MAX = GObject.G_MAXSHORT - MIN = GObject.G_MINSHORT + MAX = GLib.MAXSHORT + MIN = GLib.MINSHORT def test_short_return(self): self.assertEqual(self.MAX, GIMarshallingTests.short_return_max()) @@ -369,7 +368,7 @@ class TestShort(unittest.TestCase): class TestUShort(unittest.TestCase): - MAX = GObject.G_MAXUSHORT + MAX = GLib.MAXUSHORT def test_ushort_return(self): self.assertEqual(self.MAX, GIMarshallingTests.ushort_return()) @@ -395,8 +394,8 @@ class TestUShort(unittest.TestCase): class TestInt(unittest.TestCase): - MAX = GObject.G_MAXINT - MIN = GObject.G_MININT + MAX = GLib.MAXINT + MIN = GLib.MININT def test_int_return(self): self.assertEqual(self.MAX, GIMarshallingTests.int_return_max()) @@ -424,12 +423,12 @@ class TestInt(unittest.TestCase): def test_int_inout(self): self.assertEqual(self.MIN, GIMarshallingTests.int_inout_max_min(Number(self.MAX))) self.assertEqual(self.MAX, GIMarshallingTests.int_inout_min_max(Number(self.MIN))) - self.assertRaises(TypeError, GIMarshallingTests.int_inout_min_max, Number(self.MIN), CONSTANT_NUMBER) + self.assertRaises(TypeError, GIMarshallingTests.int_inout_min_max, Number(self.MIN), 42) class TestUInt(unittest.TestCase): - MAX = GObject.G_MAXUINT + MAX = GLib.MAXUINT def test_uint_return(self): self.assertEqual(self.MAX, GIMarshallingTests.uint_return()) @@ -455,8 +454,8 @@ class TestUInt(unittest.TestCase): class TestLong(unittest.TestCase): - MAX = GObject.G_MAXLONG - MIN = GObject.G_MINLONG + MAX = GLib.MAXLONG + MIN = GLib.MINLONG def test_long_return(self): self.assertEqual(self.MAX, GIMarshallingTests.long_return_max()) @@ -488,7 +487,7 @@ class TestLong(unittest.TestCase): class TestULong(unittest.TestCase): - MAX = GObject.G_MAXULONG + MAX = GLib.MAXULONG def test_ulong_return(self): self.assertEqual(self.MAX, GIMarshallingTests.ulong_return()) @@ -514,8 +513,8 @@ class TestULong(unittest.TestCase): class TestSSize(unittest.TestCase): - MAX = GObject.G_MAXLONG - MIN = GObject.G_MINLONG + MAX = GLib.MAXSSIZE + MIN = GLib.MINSSIZE def test_ssize_return(self): self.assertEqual(self.MAX, GIMarshallingTests.ssize_return_max()) @@ -547,7 +546,7 @@ class TestSSize(unittest.TestCase): class TestSize(unittest.TestCase): - MAX = GObject.G_MAXULONG + MAX = GLib.MAXSIZE def test_size_return(self): self.assertEqual(self.MAX, GIMarshallingTests.size_return()) @@ -589,8 +588,8 @@ class TestTimet(unittest.TestCase): class TestFloat(unittest.TestCase): - MAX = GObject.G_MAXFLOAT - MIN = GObject.G_MINFLOAT + MAX = GLib.MAXFLOAT + MIN = GLib.MINFLOAT def test_float_return(self): self.assertAlmostEqual(self.MAX, GIMarshallingTests.float_return()) @@ -609,8 +608,8 @@ class TestFloat(unittest.TestCase): class TestDouble(unittest.TestCase): - MAX = GObject.G_MAXDOUBLE - MIN = GObject.G_MINDOUBLE + MAX = GLib.MAXDOUBLE + MIN = GLib.MINDOUBLE def test_double_return(self): self.assertAlmostEqual(self.MAX, GIMarshallingTests.double_return()) @@ -636,8 +635,13 @@ class TestGType(unittest.TestCase): def check_readonly(gtype): gtype.name = "foo" - self.assertRaises(AttributeError, check_readonly, GObject.TYPE_NONE) - self.assertRaises(AttributeError, check_readonly, GObject.TYPE_STRING) + errors = (AttributeError,) + if platform.python_implementation() == "PyPy": + # https://foss.heptapod.net/pypy/pypy/-/issues/2788 + errors = (AttributeError, TypeError) + + self.assertRaises(errors, check_readonly, GObject.TYPE_NONE) + self.assertRaises(errors, check_readonly, GObject.TYPE_STRING) def test_gtype_return(self): self.assertEqual(GObject.TYPE_NONE, GIMarshallingTests.gtype_return()) @@ -659,18 +663,29 @@ class TestGType(unittest.TestCase): class TestUtf8(unittest.TestCase): + def test_utf8_as_uint8array_in(self): + data = CONSTANT_UTF8 + if not isinstance(data, bytes): + data = data.encode("utf-8") + GIMarshallingTests.utf8_as_uint8array_in(data) + def test_utf8_none_return(self): self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.utf8_none_return()) def test_utf8_full_return(self): self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.utf8_full_return()) + def test_extra_utf8_full_return_invalid(self): + with pytest.raises(UnicodeDecodeError): + GIMarshallingTests.extra_utf8_full_return_invalid() + + def test_extra_utf8_full_out_invalid(self): + with pytest.raises(UnicodeDecodeError): + GIMarshallingTests.extra_utf8_full_out_invalid() + def test_utf8_none_in(self): GIMarshallingTests.utf8_none_in(CONSTANT_UTF8) - if sys.version_info < (3, 0): - GIMarshallingTests.utf8_none_in(PY2_UNICODE_UTF8) - - self.assertRaises(TypeError, GIMarshallingTests.utf8_none_in, CONSTANT_NUMBER) + self.assertRaises(TypeError, GIMarshallingTests.utf8_none_in, 42) self.assertRaises(TypeError, GIMarshallingTests.utf8_none_in, None) def test_utf8_none_out(self): @@ -696,8 +711,19 @@ class TestFilename(unittest.TestCase): def tearDown(self): shutil.rmtree(self.workdir) + def tests_filename_list_return(self): + assert GIMarshallingTests.filename_list_return() == [] + + @unittest.skipIf(os.name == "nt", "fixme") def test_filename_in(self): - fname = os.path.join(self.workdir, _unicode('testäø.txt')) + fname = os.path.join(self.workdir, u'testäø.txt') + + try: + os.path.exists(fname) + except ValueError: + # non-unicode fs encoding + return + self.assertRaises(GLib.GError, GLib.file_get_contents, fname) with open(fname.encode('UTF-8'), 'wb') as f: @@ -707,21 +733,197 @@ class TestFilename(unittest.TestCase): self.assertEqual(result, True) self.assertEqual(contents, b'hello world!\n\x01\x02') + def test_filename_in_nullable(self): + self.assertTrue(GIMarshallingTests.filename_copy(None) is None) + self.assertRaises(TypeError, GIMarshallingTests.filename_exists, None) + + @unittest.skipIf(os.name == "nt", "fixme") def test_filename_out(self): self.assertRaises(GLib.GError, GLib.Dir.make_tmp, 'test') + name = 'testäø.XXXXXX' - dirname = GLib.Dir.make_tmp('testäø.XXXXXX') - self.assertTrue('/testäø.' in dirname, dirname) - dirname = _bytes(dirname) + try: + os.path.exists(name) + except ValueError: + # non-unicode fs encoding + return + + dirname = GLib.Dir.make_tmp(name) + self.assertTrue(os.path.sep + 'testäø.' in dirname, dirname) self.assertTrue(os.path.isdir(dirname)) os.rmdir(dirname) - def test_filename_type_error(self): - self.assertRaises(TypeError, GLib.file_get_contents, 23) + def test_wrong_types(self): + self.assertRaises(TypeError, GIMarshallingTests.filename_copy, 23) + self.assertRaises(TypeError, GIMarshallingTests.filename_copy, []) + + def test_null(self): + self.assertTrue(GIMarshallingTests.filename_copy(None) is None) + self.assertRaises(TypeError, GIMarshallingTests.filename_exists, None) + + def test_round_trip(self): + self.assertEqual(GIMarshallingTests.filename_copy(u"foo"), "foo") + self.assertEqual(GIMarshallingTests.filename_copy(b"foo"), "foo") + + def test_contains_null(self): + self.assertRaises( + (ValueError, TypeError), + GIMarshallingTests.filename_copy, b"foo\x00") + self.assertRaises( + (ValueError, TypeError), + GIMarshallingTests.filename_copy, u"foo\x00") + + def test_win32_surrogates(self): + if os.name != "nt": + return + + copy = GIMarshallingTests.filename_copy + glib_repr = GIMarshallingTests.filename_to_glib_repr + + self.assertEqual(copy(u"\ud83d"), u"\ud83d") + self.assertEqual(copy(u"\x61\uDC00"), u"\x61\uDC00") + self.assertEqual(copy(u"\uD800\uDC01"), u"\U00010001") + self.assertEqual(copy(u"\uD83D\x20\uDCA9"), u"\uD83D\x20\uDCA9") + + self.assertEqual(glib_repr(u"\ud83d"), b"\xed\xa0\xbd") + self.assertEqual(glib_repr(u"\uD800\uDC01"), b"\xf0\x90\x80\x81") + + self.assertEqual( + glib_repr(u"\uD800\uDBFF"), b"\xED\xA0\x80\xED\xAF\xBF") + self.assertEqual( + glib_repr(u"\uD800\uE000"), b"\xED\xA0\x80\xEE\x80\x80") + self.assertEqual( + glib_repr(u"\uD7FF\uDC00"), b"\xED\x9F\xBF\xED\xB0\x80") + self.assertEqual(glib_repr(u"\x61\uDC00"), b"\x61\xED\xB0\x80") + self.assertEqual(glib_repr(u"\uDC00"), b"\xED\xB0\x80") + + def test_win32_bytes_py3(self): + if not (os.name == "nt"): + return + + values = [ + b"foo", + b"\xff\xff", + b"\xc3\xb6\xc3\xa4\xc3\xbc", + b"\xed\xa0\xbd", + b"\xf0\x90\x80\x81", + ] + + for v in values: + try: + uni = v.decode(sys.getfilesystemencoding(), "surrogatepass") + except UnicodeDecodeError: + continue + self.assertEqual(GIMarshallingTests.filename_copy(v), uni) + + def test_unix_various(self): + if os.name == "nt": + return + + copy = GIMarshallingTests.filename_copy + glib_repr = GIMarshallingTests.filename_to_glib_repr + + try: + os.fsdecode(b"\xff\xfe") + except UnicodeDecodeError: + self.assertRaises(UnicodeDecodeError, copy, b"\xff\xfe") + else: + str_path = copy(b"\xff\xfe") + self.assertTrue(isinstance(str_path, str)) + self.assertEqual(str_path, os.fsdecode(b"\xff\xfe")) + self.assertEqual(copy(str_path), str_path) + self.assertEqual(glib_repr(b"\xff\xfe"), b"\xff\xfe") + self.assertEqual(glib_repr(str_path), b"\xff\xfe") + + # if getfilesystemencoding is ASCII, then we should fail like + # os.fsencode + try: + byte_path = os.fsencode(u"ä") + except UnicodeEncodeError: + self.assertRaises(UnicodeEncodeError, copy, u"ä") + else: + self.assertEqual(copy(u"ä"), u"ä") + self.assertEqual(glib_repr(u"ä"), byte_path) + + @unittest.skip("glib can't handle non-unicode paths") + def test_win32_surrogates_exists(self): + if os.name != "nt": + return + + path = os.path.join(self.workdir, u"\ud83d") + with open(path, "wb"): + self.assertTrue(os.path.exists(path)) + self.assertTrue(GIMarshallingTests.filename_exists(path)) + os.unlink(path) + + def test_path_exists_various_types(self): + wd = self.workdir + wdb = os.fsencode(wd) + + paths = [(wdb, b"foo-1"), (wd, u"foo-2"), (wd, u"öäü-3")] + + try: + paths.append((wd, os.fsdecode(b"\xff\xfe-4"))) + except UnicodeDecodeError: + # depends on the code page + pass + + if os.name != "nt": + paths.append((wdb, b"\xff\xfe-5")) + + def valid_path(p): + try: + os.path.exists(p) + except ValueError: + return False + return True + + for (d, path) in paths: + if not valid_path(path): + continue + path = os.path.join(d, path) + with open(path, "wb"): + self.assertTrue(GIMarshallingTests.filename_exists(path)) class TestArray(unittest.TestCase): + @unittest.skipUnless( + hasattr(GIMarshallingTests, "array_bool_in"), "too old gi") + def test_array_bool_in(self): + GIMarshallingTests.array_bool_in([True, False, True, True]) + + @unittest.skipUnless( + hasattr(GIMarshallingTests, "array_bool_out"), "too old gi") + def test_array_bool_out(self): + assert GIMarshallingTests.array_bool_out() == [True, False, True, True] + + @unittest.skipUnless( + hasattr(GIMarshallingTests, "array_int64_in"), "too old gi") + def test_array_int64_in(self): + GIMarshallingTests.array_int64_in([-1, 0, 1, 2]) + + @unittest.skipUnless( + hasattr(GIMarshallingTests, "array_uint64_in"), "too old gi") + def test_array_uint64_in(self): + GIMarshallingTests.array_uint64_in([GLib.MAXUINT64, 0, 1, 2]) + + @unittest.skipUnless( + hasattr(GIMarshallingTests, "array_unichar_in"), "too old gi") + def test_array_unichar_in(self): + GIMarshallingTests.array_unichar_in(list(CONSTANT_UCS4)) + GIMarshallingTests.array_unichar_in(CONSTANT_UCS4) + + @unittest.skipUnless( + hasattr(GIMarshallingTests, "array_unichar_out"), "too old gi") + def test_array_unichar_out(self): + result = list(CONSTANT_UCS4) + assert GIMarshallingTests.array_unichar_out() == result + + def test_array_zero_terminated_return_unichar(self): + assert GIMarshallingTests.array_zero_terminated_return_unichar() == \ + list(CONSTANT_UCS4) + def test_array_fixed_int_return(self): self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.array_fixed_int_return()) @@ -764,7 +966,7 @@ class TestArray(unittest.TestCase): def test_array_uint8_in(self): GIMarshallingTests.array_uint8_in(Sequence([97, 98, 99, 100])) - GIMarshallingTests.array_uint8_in(_bytes("abcd")) + GIMarshallingTests.array_uint8_in(b"abcd") def test_array_string_in(self): GIMarshallingTests.array_string_in(['foo', 'bar']) @@ -813,6 +1015,15 @@ class TestArray(unittest.TestCase): GIMarshallingTests.array_struct_in([struct1, struct2, struct3]) + def test_array_boxed_struct_in_item_marshal_failure(self): + struct1 = GIMarshallingTests.BoxedStruct() + struct1.long_ = 1 + struct2 = GIMarshallingTests.BoxedStruct() + struct2.long_ = 2 + + self.assertRaises(TypeError, GIMarshallingTests.array_struct_in, + [struct1, struct2, 'not_a_struct']) + def test_array_boxed_struct_value_in(self): struct1 = GIMarshallingTests.BoxedStruct() struct1.long_ = 1 @@ -823,6 +1034,15 @@ class TestArray(unittest.TestCase): GIMarshallingTests.array_struct_value_in([struct1, struct2, struct3]) + def test_array_boxed_struct_value_in_item_marshal_failure(self): + struct1 = GIMarshallingTests.BoxedStruct() + struct1.long_ = 1 + struct2 = GIMarshallingTests.BoxedStruct() + struct2.long_ = 2 + + self.assertRaises(TypeError, GIMarshallingTests.array_struct_value_in, + [struct1, struct2, 'not_a_struct']) + def test_array_boxed_struct_take_in(self): struct1 = GIMarshallingTests.BoxedStruct() struct1.long_ = 1 @@ -854,6 +1074,15 @@ class TestArray(unittest.TestCase): GIMarshallingTests.array_simple_struct_in([struct1, struct2, struct3]) + def test_array_simple_struct_in_item_marshal_failure(self): + struct1 = GIMarshallingTests.SimpleStruct() + struct1.long_ = 1 + struct2 = GIMarshallingTests.SimpleStruct() + struct2.long_ = 2 + + self.assertRaises(TypeError, GIMarshallingTests.array_simple_struct_in, + [struct1, struct2, 'not_a_struct']) + def test_array_multi_array_key_value_in(self): GIMarshallingTests.multi_array_key_value_in(["one", "two", "three"], [1, 2, 3]) @@ -890,6 +1119,12 @@ class TestArray(unittest.TestCase): self.assertEqual((True, ['hello']), GIMarshallingTests.init_function(['hello', 'world'])) + def test_enum_array_return_type(self): + self.assertEqual(GIMarshallingTests.enum_array_return_type(), + [GIMarshallingTests.ExtraEnum.VALUE1, + GIMarshallingTests.ExtraEnum.VALUE2, + GIMarshallingTests.ExtraEnum.VALUE3]) + class TestGStrv(unittest.TestCase): @@ -930,11 +1165,22 @@ class TestArrayGVariant(unittest.TestCase): class TestGArray(unittest.TestCase): + @unittest.skipUnless( + hasattr(GIMarshallingTests, "garray_bool_none_in"), "too old gi") + def test_garray_bool_none_in(self): + GIMarshallingTests.garray_bool_none_in([True, False, True, True]) + + @unittest.skipUnless( + hasattr(GIMarshallingTests, "garray_unichar_none_in"), "too old gi") + def test_garray_unichar_none_in(self): + GIMarshallingTests.garray_unichar_none_in(CONSTANT_UCS4) + GIMarshallingTests.garray_unichar_none_in(list(CONSTANT_UCS4)) + def test_garray_int_none_return(self): self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.garray_int_none_return()) def test_garray_uint64_none_return(self): - self.assertEqual([0, GObject.G_MAXUINT64], GIMarshallingTests.garray_uint64_none_return()) + self.assertEqual([0, GLib.MAXUINT64], GIMarshallingTests.garray_uint64_none_return()) def test_garray_utf8_none_return(self): self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_none_return()) @@ -954,7 +1200,7 @@ class TestGArray(unittest.TestCase): self.assertRaises(TypeError, GIMarshallingTests.garray_int_none_in, None) def test_garray_uint64_none_in(self): - GIMarshallingTests.garray_uint64_none_in(Sequence([0, GObject.G_MAXUINT64])) + GIMarshallingTests.garray_uint64_none_in(Sequence([0, GLib.MAXUINT64])) def test_garray_utf8_none_in(self): GIMarshallingTests.garray_utf8_none_in(Sequence(['0', '1', '2'])) @@ -1061,7 +1307,18 @@ class TestGByteArray(unittest.TestCase): self.assertEqual(b'\x001\xFF3', GIMarshallingTests.bytearray_full_return()) def test_bytearray_none_in(self): - GIMarshallingTests.bytearray_none_in(b'\x00\x31\xFF\x33') + b = b'\x00\x31\xFF\x33' + ba = GLib.ByteArray.new_take(b) + + # b should always have the same value even + # though the generated GByteArray is being modified + GIMarshallingTests.bytearray_none_in(b) + GIMarshallingTests.bytearray_none_in(b) + + # The GByteArray is just a bytes + # thus it will not reflect any changes + GIMarshallingTests.bytearray_none_in(ba) + GIMarshallingTests.bytearray_none_in(ba) class TestGList(unittest.TestCase): @@ -1070,7 +1327,7 @@ class TestGList(unittest.TestCase): self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.glist_int_none_return()) def test_glist_uint32_none_return(self): - self.assertEqual([0, GObject.G_MAXUINT32], GIMarshallingTests.glist_uint32_none_return()) + self.assertEqual([0, GLib.MAXUINT32], GIMarshallingTests.glist_uint32_none_return()) def test_glist_utf8_none_return(self): self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_none_return()) @@ -1089,8 +1346,16 @@ class TestGList(unittest.TestCase): self.assertRaises(TypeError, GIMarshallingTests.glist_int_none_in, 42) self.assertRaises(TypeError, GIMarshallingTests.glist_int_none_in, None) + def test_glist_int_none_in_error_getitem(self): + + class FailingSequence(Sequence): + def __getitem__(self, key): + raise Exception + + self.assertRaises(Exception, GIMarshallingTests.glist_int_none_in, FailingSequence((-1, 0, 1, 2))) + def test_glist_uint32_none_in(self): - GIMarshallingTests.glist_uint32_none_in(Sequence((0, GObject.G_MAXUINT32))) + GIMarshallingTests.glist_uint32_none_in(Sequence((0, GLib.MAXUINT32))) def test_glist_utf8_none_in(self): GIMarshallingTests.glist_utf8_none_in(Sequence(('0', '1', '2'))) @@ -1136,6 +1401,14 @@ class TestGSList(unittest.TestCase): self.assertRaises(TypeError, GIMarshallingTests.gslist_int_none_in, 42) self.assertRaises(TypeError, GIMarshallingTests.gslist_int_none_in, None) + def test_gslist_int_none_in_error_getitem(self): + + class FailingSequence(Sequence): + def __getitem__(self, key): + raise Exception + + self.assertRaises(Exception, GIMarshallingTests.gslist_int_none_in, FailingSequence((-1, 0, 1, 2))) + def test_gslist_utf8_none_in(self): GIMarshallingTests.gslist_utf8_none_in(Sequence(('0', '1', '2'))) @@ -1160,6 +1433,26 @@ class TestGSList(unittest.TestCase): class TestGHashTable(unittest.TestCase): + @unittest.skip("broken") + def test_ghashtable_double_in(self): + GIMarshallingTests.ghashtable_double_in( + {"-1": -0.1, "0": 0.0, "1": 0.1, "2": 0.2}) + + @unittest.skip("broken") + def test_ghashtable_float_in(self): + GIMarshallingTests.ghashtable_float_in( + {"-1": -0.1, "0": 0.0, "1": 0.1, "2": 0.2}) + + @unittest.skip("broken") + def test_ghashtable_int64_in(self): + GIMarshallingTests.ghashtable_int64_in( + {"-1": GLib.MAXUINT32 + 1, "0": 0, "1": 1, "2": 2}) + + @unittest.skip("broken") + def test_ghashtable_uint64_in(self): + GIMarshallingTests.ghashtable_uint64_in( + {"-1": GLib.MAXUINT32 + 1, "0": 0, "1": 1, "2": 2}) + def test_ghashtable_int_none_return(self): self.assertEqual({-1: 1, 0: 0, 1: -1, 2: -2}, GIMarshallingTests.ghashtable_int_none_return()) @@ -1208,6 +1501,17 @@ class TestGHashTable(unittest.TestCase): self.assertEqual({'-1': '1', '0': '0', '1': '1'}, GIMarshallingTests.ghashtable_utf8_full_inout(i)) + def test_ghashtable_enum_none_in(self): + GIMarshallingTests.ghashtable_enum_none_in({1: GIMarshallingTests.ExtraEnum.VALUE1, + 2: GIMarshallingTests.ExtraEnum.VALUE2, + 3: GIMarshallingTests.ExtraEnum.VALUE3}) + + def test_ghashtable_enum_none_return(self): + self.assertEqual({1: GIMarshallingTests.ExtraEnum.VALUE1, + 2: GIMarshallingTests.ExtraEnum.VALUE2, + 3: GIMarshallingTests.ExtraEnum.VALUE3}, + GIMarshallingTests.ghashtable_enum_none_return()) + class TestGValue(unittest.TestCase): @@ -1219,15 +1523,13 @@ class TestGValue(unittest.TestCase): value = GObject.Value(GObject.TYPE_INT, 42) GIMarshallingTests.gvalue_in(value) - @unittest.skipUnless(hasattr(GIMarshallingTests, 'gvalue_in_with_modification'), - 'Newer version of gi needed.') def test_gvalue_in_with_modification(self): value = GObject.Value(GObject.TYPE_INT, 42) GIMarshallingTests.gvalue_in_with_modification(value) self.assertEqual(value.get_int(), 24) def test_gvalue_int64_in(self): - value = GObject.Value(GObject.TYPE_INT64, GObject.G_MAXINT64) + value = GObject.Value(GObject.TYPE_INT64, GLib.MAXINT64) GIMarshallingTests.gvalue_int64_in(value) def test_gvalue_in_with_type(self): @@ -1247,7 +1549,7 @@ class TestGValue(unittest.TestCase): self.assertEqual(42, GIMarshallingTests.gvalue_out()) def test_gvalue_int64_out(self): - self.assertEqual(GObject.G_MAXINT64, GIMarshallingTests.gvalue_int64_out()) + self.assertEqual(GLib.MAXINT64, GIMarshallingTests.gvalue_int64_out()) def test_gvalue_out_caller_allocates(self): self.assertEqual(42, GIMarshallingTests.gvalue_out_caller_allocates()) @@ -1261,10 +1563,46 @@ class TestGValue(unittest.TestCase): # the function already asserts the correct values GIMarshallingTests.gvalue_flat_array([42, "42", True]) + def test_gvalue_flat_array_in_item_marshal_failure(self): + # Tests the failure to marshal 2^256 to a GValue mid-way through the array marshaling. + self.assertRaises(OverflowError, GIMarshallingTests.gvalue_flat_array, + [42, 2 ** 256, True]) + + self.assertRaises(OverflowError, GIMarshallingTests.gvalue_flat_array, + [GLib.MAXINT + 1, "42", True]) + self.assertRaises(OverflowError, GIMarshallingTests.gvalue_flat_array, + [GLib.MININT - 1, "42", True]) + + # FIXME: https://gitlab.gnome.org/GNOME/pygobject/-/issues/582#note_1764164 + exc_prefix = "Item 0: " if sys.version_info[:2] < (3, 12) else "" + + with pytest.raises( + OverflowError, + match=exc_prefix + '%d not in range %d to %d' % ( + GLib.MAXINT + 1, GLib.MININT, GLib.MAXINT)): + GIMarshallingTests.gvalue_flat_array([GLib.MAXINT + 1, "42", True]) + + min_, max_ = GLib.MININT, GLib.MAXINT + + with pytest.raises( + OverflowError, + match=exc_prefix + '%d not in range %d to %d' % ( + GLib.MAXUINT64 * 2, min_, max_)): + GIMarshallingTests.gvalue_flat_array([GLib.MAXUINT64 * 2, "42", True]) + def test_gvalue_flat_array_out(self): values = GIMarshallingTests.return_gvalue_flat_array() self.assertEqual(values, [42, '42', True]) + def test_gvalue_gobject_ref_counts_simple(self): + obj = GObject.Object() + grefcount = obj.__grefcount__ + value = GObject.Value(GObject.TYPE_OBJECT, obj) + del value + gc.collect() + gc.collect() + assert obj.__grefcount__ == grefcount + def test_gvalue_gobject_ref_counts(self): # Tests a GObject held by a GValue obj = GObject.Object() @@ -1296,12 +1634,14 @@ class TestGValue(unittest.TestCase): del res del value gc.collect() + gc.collect() self.assertEqual(obj.__grefcount__, grefcount) del obj gc.collect() self.assertEqual(ref(), None) + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") def test_gvalue_boxed_ref_counts(self): # Tests a boxed type wrapping a python object pointer (TYPE_PYOBJECT) # held by a GValue @@ -1342,8 +1682,8 @@ class TestGValue(unittest.TestCase): gc.collect() self.assertEqual(ref(), None) - # FIXME: crashes - def disabled_test_gvalue_flat_array_round_trip(self): + @unittest.skip("broken") + def test_gvalue_flat_array_round_trip(self): self.assertEqual([42, '42', True], GIMarshallingTests.gvalue_flat_array_round_trip(42, '42', True)) @@ -1353,6 +1693,22 @@ class TestGClosure(unittest.TestCase): def test_in(self): GIMarshallingTests.gclosure_in(lambda: 42) + def test_in_partial(self): + from functools import partial + + called_args = [] + called_kwargs = {} + + def callback(*args, **kwargs): + called_args.extend(args) + called_kwargs.update(kwargs) + return 42 + + func = partial(callback, 1, 2, 3, foo=42) + GIMarshallingTests.gclosure_in(func) + assert called_args == [1, 2, 3] + assert called_kwargs["foo"] == 42 + def test_pass(self): # test passing a closure between two C calls closure = GIMarshallingTests.gclosure_return() @@ -1402,31 +1758,6 @@ class TestPointer(unittest.TestCase): class TestEnum(unittest.TestCase): - @classmethod - def setUpClass(cls): - '''Run tests under a test locale. - - Upper case conversion of member names should not be locale specific - e. g. in Turkish, "i".upper() == "i", which gives results like "iNVALiD" - - Run test under a locale which defines toupper('a') == 'a' - ''' - cls.locale_dir = tempfile.mkdtemp() - src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'te_ST@nouppera') - dest = os.path.join(cls.locale_dir, 'te_ST.UTF-8@nouppera') - subprocess.check_call(['localedef', '-i', src, '-c', '-f', 'UTF-8', dest]) - os.environ['LOCPATH'] = cls.locale_dir - locale.setlocale(locale.LC_ALL, 'te_ST.UTF-8@nouppera') - - @classmethod - def tearDownClass(cls): - locale.setlocale(locale.LC_ALL, 'C') - shutil.rmtree(cls.locale_dir) - try: - del os.environ['LOCPATH'] - except KeyError: - pass - def test_enum(self): self.assertTrue(issubclass(GIMarshallingTests.Enum, int)) self.assertTrue(isinstance(GIMarshallingTests.Enum.VALUE1, GIMarshallingTests.Enum)) @@ -1479,22 +1810,25 @@ class TestEnum(unittest.TestCase): self.assertEqual(GIMarshallingTests.Enum.__gtype__.name, 'PyGIMarshallingTestsEnum') - def test_enum_double_registration_error(self): - # a warning is printed for double registration and pygobject will - # also raise a RuntimeError. - old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_ERROR) - try: - self.assertRaises(RuntimeError, - gi._gi.enum_register_new_gtype_and_add, - GIMarshallingTests.Enum.__info__) - finally: - GLib.log_set_always_fatal(old_mask) - def test_enum_add_type_error(self): self.assertRaises(TypeError, gi._gi.enum_add, GIMarshallingTests.NoTypeFlags.__gtype__) + def test_type_module_name(self): + self.assertEqual(GIMarshallingTests.Enum.__name__, "Enum") + self.assertEqual(GIMarshallingTests.Enum.__module__, + "gi.repository.GIMarshallingTests") + + def test_hash(self): + assert (hash(GIMarshallingTests.Enum.VALUE1) == + hash(GIMarshallingTests.Enum(GIMarshallingTests.Enum.VALUE1))) + + def test_repr(self): + self.assertEqual(repr(GIMarshallingTests.Enum.VALUE3), + "<enum GI_MARSHALLING_TESTS_ENUM_VALUE3 of type " + "GIMarshallingTests.Enum>") + class TestEnumVFuncResults(unittest.TestCase): class EnumTester(GIMarshallingTests.Object): @@ -1522,6 +1856,12 @@ class TestGEnum(unittest.TestCase): self.assertTrue(isinstance(GIMarshallingTests.GEnum.VALUE3, GIMarshallingTests.GEnum)) self.assertEqual(42, GIMarshallingTests.GEnum.VALUE3) + def test_pickle(self): + v = GIMarshallingTests.GEnum.VALUE3 + new_v = pickle.loads(pickle.dumps(v)) + assert new_v == v + assert isinstance(new_v, GIMarshallingTests.GEnum) + def test_value_nick_and_name(self): self.assertEqual(GIMarshallingTests.GEnum.VALUE1.value_nick, 'value1') self.assertEqual(GIMarshallingTests.GEnum.VALUE2.value_nick, 'value2') @@ -1534,6 +1874,7 @@ class TestGEnum(unittest.TestCase): def test_genum_in(self): GIMarshallingTests.genum_in(GIMarshallingTests.GEnum.VALUE3) GIMarshallingTests.genum_in(42) + GIMarshallingTests.GEnum.in_(42) self.assertRaises(TypeError, GIMarshallingTests.genum_in, 43) self.assertRaises(TypeError, GIMarshallingTests.genum_in, 'GIMarshallingTests.GEnum.VALUE3') @@ -1545,6 +1886,7 @@ class TestGEnum(unittest.TestCase): def test_genum_out(self): genum = GIMarshallingTests.genum_out() + genum = GIMarshallingTests.GEnum.out() self.assertTrue(isinstance(genum, GIMarshallingTests.GEnum)) self.assertEqual(genum, GIMarshallingTests.GEnum.VALUE3) @@ -1553,6 +1895,20 @@ class TestGEnum(unittest.TestCase): self.assertTrue(isinstance(genum, GIMarshallingTests.GEnum)) self.assertEqual(genum, GIMarshallingTests.GEnum.VALUE1) + def test_type_module_name(self): + self.assertEqual(GIMarshallingTests.GEnum.__name__, "GEnum") + self.assertEqual(GIMarshallingTests.GEnum.__module__, + "gi.repository.GIMarshallingTests") + + def test_hash(self): + assert (hash(GIMarshallingTests.GEnum.VALUE3) == + hash(GIMarshallingTests.GEnum(GIMarshallingTests.GEnum.VALUE3))) + + def test_repr(self): + self.assertEqual(repr(GIMarshallingTests.GEnum.VALUE3), + "<enum GI_MARSHALLING_TESTS_GENUM_VALUE3 of type " + "GIMarshallingTests.GEnum>") + class TestGFlags(unittest.TestCase): @@ -1577,9 +1933,11 @@ class TestGFlags(unittest.TestCase): def test_flags_in(self): GIMarshallingTests.flags_in(GIMarshallingTests.Flags.VALUE2) + GIMarshallingTests.Flags.in_(GIMarshallingTests.Flags.VALUE2) # result of __or__() operation should still be valid instance, not an int. GIMarshallingTests.flags_in(GIMarshallingTests.Flags.VALUE2 | GIMarshallingTests.Flags.VALUE2) GIMarshallingTests.flags_in_zero(Number(0)) + GIMarshallingTests.Flags.in_zero(Number(0)) self.assertRaises(TypeError, GIMarshallingTests.flags_in, 1 << 1) self.assertRaises(TypeError, GIMarshallingTests.flags_in, 'GIMarshallingTests.Flags.VALUE2') @@ -1589,6 +1947,11 @@ class TestGFlags(unittest.TestCase): self.assertTrue(isinstance(flags, GIMarshallingTests.Flags)) self.assertEqual(flags, GIMarshallingTests.Flags.VALUE2) + def test_flags_return_method(self): + flags = GIMarshallingTests.Flags.returnv() + self.assertTrue(isinstance(flags, GIMarshallingTests.Flags)) + self.assertEqual(flags, GIMarshallingTests.Flags.VALUE2) + def test_flags_out(self): flags = GIMarshallingTests.flags_out() self.assertTrue(isinstance(flags, GIMarshallingTests.Flags)) @@ -1599,6 +1962,24 @@ class TestGFlags(unittest.TestCase): self.assertTrue(isinstance(flags, GIMarshallingTests.Flags)) self.assertEqual(flags, GIMarshallingTests.Flags.VALUE1) + def test_type_module_name(self): + self.assertEqual(GIMarshallingTests.Flags.__name__, "Flags") + self.assertEqual(GIMarshallingTests.Flags.__module__, + "gi.repository.GIMarshallingTests") + + def test_repr(self): + self.assertEqual(repr(GIMarshallingTests.Flags.VALUE2), + "<flags GI_MARSHALLING_TESTS_FLAGS_VALUE2 of type " + "GIMarshallingTests.Flags>") + + def test_hash(self): + assert (hash(GIMarshallingTests.Flags.VALUE2) == + hash(GIMarshallingTests.Flags(GIMarshallingTests.Flags.VALUE2))) + + def test_flags_large_in(self): + GIMarshallingTests.extra_flags_large_in( + GIMarshallingTests.ExtraFlags.VALUE2) + class TestNoTypeFlags(unittest.TestCase): @@ -1648,16 +2029,16 @@ class TestNoTypeFlags(unittest.TestCase): self.assertEqual(GIMarshallingTests.NoTypeFlags.__gtype__.name, 'PyGIMarshallingTestsNoTypeFlags') - def test_flags_double_registration_error(self): - # a warning is printed for double registration and pygobject will - # also raise a RuntimeError. - old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_ERROR) - try: - self.assertRaises(RuntimeError, - gi._gi.flags_register_new_gtype_and_add, - GIMarshallingTests.NoTypeFlags.__info__) - finally: - GLib.log_set_always_fatal(old_mask) + def test_type_module_name(self): + self.assertEqual(GIMarshallingTests.NoTypeFlags.__name__, + "NoTypeFlags") + self.assertEqual(GIMarshallingTests.NoTypeFlags.__module__, + "gi.repository.GIMarshallingTests") + + def test_repr(self): + self.assertEqual(repr(GIMarshallingTests.NoTypeFlags.VALUE2), + "<flags GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2 of " + "type GIMarshallingTests.NoTypeFlags>") class TestStructure(unittest.TestCase): @@ -1833,6 +2214,19 @@ class TestStructure(unittest.TestCase): self.assertEqual(struct.long_, 42) self.assertEqual(struct.string_, 'hello') + def test_union_init(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + GIMarshallingTests.Union(42) + + self.assertTrue(issubclass(warn[0].category, DeprecationWarning)) + + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + GIMarshallingTests.Union(f=42) + + self.assertTrue(issubclass(warn[0].category, DeprecationWarning)) + def test_union(self): union = GIMarshallingTests.Union() @@ -1870,6 +2264,27 @@ class TestStructure(unittest.TestCase): self.assertRaises(TypeError, GIMarshallingTests.Union.method) + def test_repr(self): + self.assertRegex( + repr(GIMarshallingTests.PointerStruct()), + r"<GIMarshallingTests.PointerStruct object at 0x[^\s]+ " + r"\(void at 0x[^\s]+\)>") + + self.assertRegex( + repr(GIMarshallingTests.SimpleStruct()), + r"<GIMarshallingTests.SimpleStruct object at 0x[^\s]+ " + r"\(void at 0x[^\s]+\)>") + + self.assertRegex( + repr(GIMarshallingTests.Union()), + r"<GIMarshallingTests.Union object at 0x[^\s]+ " + r"\(GIMarshallingTestsUnion at 0x[^\s]+\)>") + + self.assertRegex( + repr(GIMarshallingTests.BoxedStruct()), + r"<GIMarshallingTests.BoxedStruct object at 0x[^\s]+ " + r"\(GIMarshallingTestsBoxedStruct at 0x[^\s]+\)>") + class TestGObject(unittest.TestCase): @@ -1987,6 +2402,9 @@ class TestGObject(unittest.TestCase): GIMarshallingTests.Object.none_inout(GIMarshallingTests.SubObject(int=42)) def test_object_full_inout(self): + # Using gimarshallingtests.c from GI versions > 1.38.0 will show this + # test as an "unexpected success" due to reference leak fixes in that file. + # TODO: remove the expectedFailure once PyGI relies on GI > 1.38.0. object_ = GIMarshallingTests.Object(int=42) new_object = GIMarshallingTests.Object.full_inout(object_) @@ -1994,9 +2412,21 @@ class TestGObject(unittest.TestCase): self.assertFalse(object_ is new_object) - self.assertEqual(object_.__grefcount__, 2) + self.assertEqual(object_.__grefcount__, 1) self.assertEqual(new_object.__grefcount__, 1) + def test_repr(self): + self.assertRegex( + repr(GIMarshallingTests.Object(int=42)), + r"<GIMarshallingTests.Object object at 0x[^\s]+ " + r"\(GIMarshallingTestsObject at 0x[^\s]+\)>") + + def test_nongir_repr(self): + self.assertRegex( + repr(Gio.File.new_for_path("/")), + r"<__gi__.GLocalFile object at 0x[^\s]+ " + r"\(GLocalFile at 0x[^\s]+\)>") + # FIXME: Doesn't actually return the same object. # def test_object_inout_same(self): # object_ = GIMarshallingTests.Object() @@ -2086,6 +2516,12 @@ class TestPythonGObject(unittest.TestCase): object_ = self.Object(int=42) self.assertTrue(isinstance(object_, self.Object)) + @unittest.skipUnless(hasattr(GIMarshallingTests.Object, 'new_fail'), + 'Requires newer version of GI') + def test_object_fail(self): + with self.assertRaises(GLib.Error): + GIMarshallingTests.Object.new_fail(int_=42) + def test_object_method(self): self.Object(int=0).method() @@ -2125,6 +2561,7 @@ class TestPythonGObject(unittest.TestCase): object_.method_with_default_implementation(84) self.assertEqual(object_.props.int, 84) + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") def test_vfunc_return_ref_count(self): obj = self.Object(int=42) ref_count = sys.getrefcount(obj.return_for_caller_allocated_out_parameter) @@ -2138,6 +2575,12 @@ class TestPythonGObject(unittest.TestCase): self.assertEqual(sys.getrefcount(obj.return_for_caller_allocated_out_parameter), ref_count) + def test_vfunc_return_no_ref_count(self): + obj = self.Object(int=42) + ret = obj.vfunc_caller_allocated_out_parameter() + self.assertEqual(ret, obj.return_for_caller_allocated_out_parameter) + self.assertFalse(ret is obj.return_for_caller_allocated_out_parameter) + def test_subobject_parent_vfunc(self): object_ = self.SubObject(int=81) object_.method_with_default_implementation(87) @@ -2147,10 +2590,6 @@ class TestPythonGObject(unittest.TestCase): object_ = self.SubObject(int=1) self.assertEqual(object_.vfunc_return_value_only(), 2121) - def test_dynamic_module(self): - from gi.module import DynamicModule - self.assertTrue(isinstance(GObject, DynamicModule)) - def test_subobject_non_vfunc_do_method(self): class PythonObjectWithNonVFuncDoMethod(object): def do_not_a_vfunc(self): @@ -2241,7 +2680,23 @@ class TestPythonGObject(unittest.TestCase): def test_exception_in_vfunc_return_value(self): obj = self.ErrorObject() - self.assertEqual(obj.vfunc_return_value_only(), 0) + with capture_exceptions() as exc: + self.assertEqual(obj.vfunc_return_value_only(), 0) + self.assertEqual(len(exc), 1) + self.assertEqual(exc[0].type, ValueError) + + @unittest.skipUnless(hasattr(GIMarshallingTests, 'callback_owned_boxed'), + 'requires newer version of GI') + def test_callback_owned_box(self): + def callback(box, data): + self.box = box + + def nop_callback(box, data): + pass + + GIMarshallingTests.callback_owned_boxed(callback, None) + GIMarshallingTests.callback_owned_boxed(nop_callback, None) + self.assertEqual(self.box.long_, 1) class TestMultiOutputArgs(unittest.TestCase): @@ -2268,6 +2723,11 @@ class TestInterfaces(unittest.TestCase): def setUp(self): self.instance = self.TestInterfaceImpl() + def test_iface_impl(self): + instance = GIMarshallingTests.InterfaceImpl() + assert instance.get_as_interface() is instance + instance.test_int8_in(42) + def test_wrapper(self): self.assertTrue(issubclass(GIMarshallingTests.Interface, GObject.GInterface)) self.assertEqual(GIMarshallingTests.Interface.__gtype__.name, 'GIMarshallingTestsInterface') @@ -2383,7 +2843,7 @@ class TestMRO(unittest.TestCase): pass expected = (E, D, B, C, A, GIMarshallingTests.Object, - GObject.Object, GObject.Object.__base__, gi._gobject.GObject, + GObject.Object, GObject.Object.__base__, gi._gi.GObject, object) self.assertEqual(expected, E.__mro__) @@ -2415,10 +2875,7 @@ class TestMRO(unittest.TestCase): # style mixin. type('GIWithOldStyleMixin', (GIMarshallingTests.Object, Mixin), {}) - if sys.version_info < (3, 0): - self.assertTrue(issubclass(warn[0].category, RuntimeWarning)) - else: - self.assertEqual(len(warn), 0) + self.assertEqual(len(warn), 0) class TestInterfaceClash(unittest.TestCase): @@ -2483,9 +2940,7 @@ class TestOverrides(unittest.TestCase): # not overridden self.assertEqual(GIMarshallingTests.SubObject.__module__, 'gi.repository.GIMarshallingTests') - # FIXME: does not work with TEST_NAMES='test_thread test_gi.TestOverrides', - # it is importlib._bootstrap then - #self.assertEqual(GObject.InitiallyUnowned.__module__, 'gi.repository.GObject') + self.assertEqual(GObject.InitiallyUnowned.__module__, 'gi.repository.GObject') class TestDir(unittest.TestCase): @@ -2513,55 +2968,6 @@ class TestDir(unittest.TestCase): # self.assertTrue('DoNotImportDummyTests' in list) -class TestGError(unittest.TestCase): - def test_array_in_crash(self): - # Previously there was a bug in invoke, in which C arrays were unwrapped - # from inside GArrays to be passed to the C function. But when a GError was - # set, invoke would attempt to free the C array as if it were a GArray. - # This crash is only for C arrays. It does not happen for C functions which - # take in GArrays. See https://bugzilla.gnome.org/show_bug.cgi?id=642708 - self.assertRaises(GObject.GError, GIMarshallingTests.gerror_array_in, [1, 2, 3]) - - def test_out(self): - # See https://bugzilla.gnome.org/show_bug.cgi?id=666098 - error, debug = GIMarshallingTests.gerror_out() - - self.assertIsInstance(error, GObject.GError) - self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) - self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE) - self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) - self.assertEqual(debug, GIMarshallingTests.CONSTANT_GERROR_DEBUG_MESSAGE) - - def test_out_transfer_none(self): - # See https://bugzilla.gnome.org/show_bug.cgi?id=666098 - error, debug = GIMarshallingTests.gerror_out_transfer_none() - - self.assertIsInstance(error, GObject.GError) - self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) - self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE) - self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) - self.assertEqual(GIMarshallingTests.CONSTANT_GERROR_DEBUG_MESSAGE, debug) - - def test_return(self): - # See https://bugzilla.gnome.org/show_bug.cgi?id=666098 - error = GIMarshallingTests.gerror_return() - - self.assertIsInstance(error, GObject.GError) - self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) - self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE) - self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) - - def test_exception(self): - self.assertRaises(GObject.GError, GIMarshallingTests.gerror) - try: - GIMarshallingTests.gerror() - except Exception: - etype, e = sys.exc_info()[:2] - self.assertEqual(e.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN) - self.assertEqual(e.code, GIMarshallingTests.CONSTANT_GERROR_CODE) - self.assertEqual(e.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE) - - class TestParamSpec(unittest.TestCase): # https://bugzilla.gnome.org/show_bug.cgi?id=682355 @unittest.expectedFailure @@ -2611,29 +3017,29 @@ class TestKeywordArgs(unittest.TestCase): def test_type_errors(self): # test too few args - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 arguments (0 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (0 given)", GIMarshallingTests.int_three_in_three_out) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 arguments (1 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (1 given)", GIMarshallingTests.int_three_in_three_out, 1) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 arguments (0 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (0 given)", GIMarshallingTests.int_three_in_three_out, *()) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 arguments (0 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (0 given)", GIMarshallingTests.int_three_in_three_out, *(), **{}) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 non-keyword arguments (0 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 non-keyword arguments (0 given)", GIMarshallingTests.int_three_in_three_out, *(), **{'c': 4}) # test too many args - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 arguments (4 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (4 given)", GIMarshallingTests.int_three_in_three_out, *(1, 2, 3, 4)) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() takes exactly 3 non-keyword arguments (4 given)", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 non-keyword arguments (4 given)", GIMarshallingTests.int_three_in_three_out, *(1, 2, 3, 4), c=6) # test too many keyword args - self.assertRaisesMessage(TypeError, "int_three_in_three_out() got multiple values for keyword argument 'a'", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() got multiple values for keyword argument 'a'", GIMarshallingTests.int_three_in_three_out, 1, 2, 3, **{'a': 4, 'b': 5}) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() got an unexpected keyword argument 'd'", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() got an unexpected keyword argument 'd'", GIMarshallingTests.int_three_in_three_out, d=4) - self.assertRaisesMessage(TypeError, "int_three_in_three_out() got an unexpected keyword argument 'e'", + self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() got an unexpected keyword argument 'e'", GIMarshallingTests.int_three_in_three_out, **{'e': 2}) def test_kwargs_are_not_modified(self): @@ -2642,214 +3048,35 @@ class TestKeywordArgs(unittest.TestCase): GIMarshallingTests.int_three_in_three_out(1, c=4, **d) self.assertEqual(d, d2) + @unittest.skipUnless(hasattr(GIMarshallingTests, 'int_one_in_utf8_two_in_one_allows_none'), + 'Requires newer GIMarshallingTests') + def test_allow_none_as_default(self): + GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', '4') + GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3') + GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2) + GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, d='4') -class TestPropertiesObject(unittest.TestCase): - - def setUp(self): - self.obj = GIMarshallingTests.PropertiesObject() - - def test_boolean(self): - self.assertEqual(self.obj.props.some_boolean, False) - self.obj.props.some_boolean = True - self.assertEqual(self.obj.props.some_boolean, True) - - obj = GIMarshallingTests.PropertiesObject(some_boolean=True) - self.assertEqual(obj.props.some_boolean, True) - - def test_char(self): - self.assertEqual(self.obj.props.some_char, 0) - self.obj.props.some_char = GObject.G_MAXINT8 - self.assertEqual(self.obj.props.some_char, GObject.G_MAXINT8) - - obj = GIMarshallingTests.PropertiesObject(some_char=-42) - self.assertEqual(obj.props.some_char, -42) - - def test_uchar(self): - self.assertEqual(self.obj.props.some_uchar, 0) - self.obj.props.some_uchar = GObject.G_MAXUINT8 - self.assertEqual(self.obj.props.some_uchar, GObject.G_MAXUINT8) - - obj = GIMarshallingTests.PropertiesObject(some_uchar=42) - self.assertEqual(obj.props.some_uchar, 42) - - def test_int(self): - self.assertEqual(self.obj.props.some_int, 0) - self.obj.props.some_int = GObject.G_MAXINT - self.assertEqual(self.obj.props.some_int, GObject.G_MAXINT) - - obj = GIMarshallingTests.PropertiesObject(some_int=-42) - self.assertEqual(obj.props.some_int, -42) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_int', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_int', None) - - self.assertEqual(obj.props.some_int, -42) - - def test_uint(self): - self.assertEqual(self.obj.props.some_uint, 0) - self.obj.props.some_uint = GObject.G_MAXUINT - self.assertEqual(self.obj.props.some_uint, GObject.G_MAXUINT) - - obj = GIMarshallingTests.PropertiesObject(some_uint=42) - self.assertEqual(obj.props.some_uint, 42) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_uint', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_uint', None) - - self.assertEqual(obj.props.some_uint, 42) - - def test_long(self): - self.assertEqual(self.obj.props.some_long, 0) - self.obj.props.some_long = GObject.G_MAXLONG - self.assertEqual(self.obj.props.some_long, GObject.G_MAXLONG) - - obj = GIMarshallingTests.PropertiesObject(some_long=-42) - self.assertEqual(obj.props.some_long, -42) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_long', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_long', None) - - self.assertEqual(obj.props.some_long, -42) - - def test_ulong(self): - self.assertEqual(self.obj.props.some_ulong, 0) - self.obj.props.some_ulong = GObject.G_MAXULONG - self.assertEqual(self.obj.props.some_ulong, GObject.G_MAXULONG) - - obj = GIMarshallingTests.PropertiesObject(some_ulong=42) - self.assertEqual(obj.props.some_ulong, 42) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_ulong', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_ulong', None) - - self.assertEqual(obj.props.some_ulong, 42) - - def test_int64(self): - self.assertEqual(self.obj.props.some_int64, 0) - self.obj.props.some_int64 = GObject.G_MAXINT64 - self.assertEqual(self.obj.props.some_int64, GObject.G_MAXINT64) - - obj = GIMarshallingTests.PropertiesObject(some_int64=-4200000000000000) - self.assertEqual(obj.props.some_int64, -4200000000000000) - - def test_uint64(self): - self.assertEqual(self.obj.props.some_uint64, 0) - self.obj.props.some_uint64 = GObject.G_MAXUINT64 - self.assertEqual(self.obj.props.some_uint64, GObject.G_MAXUINT64) - - obj = GIMarshallingTests.PropertiesObject(some_uint64=4200000000000000) - self.assertEqual(obj.props.some_uint64, 4200000000000000) - - def test_float(self): - self.assertEqual(self.obj.props.some_float, 0) - self.obj.props.some_float = GObject.G_MAXFLOAT - self.assertEqual(self.obj.props.some_float, GObject.G_MAXFLOAT) - - obj = GIMarshallingTests.PropertiesObject(some_float=42.42) - self.assertAlmostEqual(obj.props.some_float, 42.42, 4) - - obj = GIMarshallingTests.PropertiesObject(some_float=42) - self.assertAlmostEqual(obj.props.some_float, 42.0, 4) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_float', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_float', None) - - self.assertAlmostEqual(obj.props.some_float, 42.0, 4) - - def test_double(self): - self.assertEqual(self.obj.props.some_double, 0) - self.obj.props.some_double = GObject.G_MAXDOUBLE - self.assertEqual(self.obj.props.some_double, GObject.G_MAXDOUBLE) - - obj = GIMarshallingTests.PropertiesObject(some_double=42.42) - self.assertAlmostEqual(obj.props.some_double, 42.42) - - obj = GIMarshallingTests.PropertiesObject(some_double=42) - self.assertAlmostEqual(obj.props.some_double, 42.0) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_double', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_double', None) - - self.assertAlmostEqual(obj.props.some_double, 42.0) - - def test_strv(self): - self.assertEqual(self.obj.props.some_strv, []) - self.obj.props.some_strv = ['hello', 'world'] - self.assertEqual(self.obj.props.some_strv, ['hello', 'world']) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_strv', 1) - self.assertRaises(TypeError, setattr, self.obj.props, 'some_strv', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_strv', [1, 2]) - self.assertRaises(TypeError, setattr, self.obj.props, 'some_strv', ['foo', 1]) - - self.assertEqual(self.obj.props.some_strv, ['hello', 'world']) - - obj = GIMarshallingTests.PropertiesObject(some_strv=['hello', 'world']) - self.assertEqual(obj.props.some_strv, ['hello', 'world']) - - def test_boxed_struct(self): - self.assertEqual(self.obj.props.some_boxed_struct, None) - - class GStrv(list): - __gtype__ = GObject.TYPE_STRV - - struct1 = GIMarshallingTests.BoxedStruct() - struct1.long_ = 1 - - self.obj.props.some_boxed_struct = struct1 - self.assertEqual(self.obj.props.some_boxed_struct.long_, 1) - self.assertEqual(self.obj.some_boxed_struct.long_, 1) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_boxed_struct', 1) - self.assertRaises(TypeError, setattr, self.obj.props, 'some_boxed_struct', 'foo') - - obj = GIMarshallingTests.PropertiesObject(some_boxed_struct=struct1) - self.assertEqual(obj.props.some_boxed_struct.long_, 1) - - def test_boxed_glist(self): - self.assertEqual(self.obj.props.some_boxed_glist, []) - - l = [GObject.G_MININT, 42, GObject.G_MAXINT] - self.obj.props.some_boxed_glist = l - self.assertEqual(self.obj.props.some_boxed_glist, l) - self.obj.props.some_boxed_glist = [] - self.assertEqual(self.obj.props.some_boxed_glist, []) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_boxed_glist', 1) - self.assertRaises(TypeError, setattr, self.obj.props, 'some_boxed_glist', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_boxed_glist', ['a']) - - @unittest.expectedFailure - def test_boxed_glist_ctor(self): - l = [GObject.G_MININT, 42, GObject.G_MAXINT] - obj = GIMarshallingTests.PropertiesObject(some_boxed_glist=l) - self.assertEqual(obj.props.some_boxed_glist, l) - - def test_variant(self): - self.assertEqual(self.obj.props.some_variant, None) - - self.obj.props.some_variant = GLib.Variant('o', '/myobj') - self.assertEqual(self.obj.props.some_variant.get_type_string(), 'o') - self.assertEqual(self.obj.props.some_variant.print_(False), "'/myobj'") - - self.obj.props.some_variant = None - self.assertEqual(self.obj.props.some_variant, None) - - obj = GIMarshallingTests.PropertiesObject(some_variant=GLib.Variant('b', True)) - self.assertEqual(obj.props.some_variant.get_type_string(), 'b') - self.assertEqual(obj.props.some_variant.get_boolean(), True) - - self.assertRaises(TypeError, setattr, self.obj.props, 'some_variant', 'foo') - self.assertRaises(TypeError, setattr, self.obj.props, 'some_variant', 23) + GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2]) + GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], '2') + self.assertRaises(TypeError, + GIMarshallingTests.array_in_utf8_two_in_out_of_order, + [-1, 0, 1, 2], a='1') + self.assertRaises(TypeError, + GIMarshallingTests.array_in_utf8_two_in_out_of_order, + [-1, 0, 1, 2]) - self.assertEqual(obj.props.some_variant.get_type_string(), 'b') - self.assertEqual(obj.props.some_variant.get_boolean(), True) + GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', '2') + GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1') + GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2]) + GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], b='2') - def test_setting_several_properties(self): - obj = GIMarshallingTests.PropertiesObject() - obj.set_properties(some_uchar=54, some_int=42) - self.assertEqual(42, obj.props.some_int) - self.assertEqual(54, obj.props.some_uchar) + GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', '3') + self.assertRaises(TypeError, + GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none, + 1, '3') + self.assertRaises(TypeError, + GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none, + 1, c='3') class TestKeywords(unittest.TestCase): @@ -2874,8 +3101,10 @@ class TestKeywords(unittest.TestCase): class TestModule(unittest.TestCase): def test_path(self): - self.assertTrue(GIMarshallingTests.__path__.endswith('GIMarshallingTests-1.0.typelib'), - GIMarshallingTests.__path__) + path = GIMarshallingTests.__path__ + assert isinstance(path, list) + assert len(path) == 1 + assert path[0].endswith('GIMarshallingTests-1.0.typelib') def test_str(self): self.assertTrue("'GIMarshallingTests' from '" in str(GIMarshallingTests), @@ -2896,16 +3125,9 @@ class TestModule(unittest.TestCase): self.assertTrue(hasattr(item, '__class__')) def test_help(self): - orig_stdout = sys.stdout - try: - if sys.version_info < (3, 0): - sys.stdout = BytesIO() - else: - sys.stdout = StringIO() + with capture_output() as (stdout, stderr): help(GIMarshallingTests) - output = sys.stdout.getvalue() - finally: - sys.stdout = orig_stdout + output = stdout.getvalue() self.assertTrue('SimpleStruct' in output, output) self.assertTrue('Interface2' in output, output) @@ -2914,7 +3136,7 @@ class TestModule(unittest.TestCase): class TestProjectVersion(unittest.TestCase): def test_version_str(self): - self.assertGreaterEqual(gi.__version__, "3.3.5") + self.assertGreater(gi.__version__, "3.") def test_version_info(self): self.assertEqual(len(gi.version_info), 3) @@ -2927,20 +3149,18 @@ class TestProjectVersion(unittest.TestCase): gi.check_version("3.3.5") -class TestObjectInfo(unittest.TestCase): - def test_get_abstract_with_abstract(self): - repo = gi.gi.Repository.get_default() - info = repo.find_by_name('GObject', 'TypeModule') - self.assertTrue(info.get_abstract()) +class TestGIWarning(unittest.TestCase): - def test_get_abstract_with_concrete(self): - repo = gi.gi.Repository.get_default() - info = repo.find_by_name('GObject', 'Object') - self.assertFalse(info.get_abstract()) + def test_warning(self): + ignored_by_default = (DeprecationWarning, PendingDeprecationWarning, + ImportWarning) - def test_get_class_struct(self): - self.assertEqual(GObject.Object.__info__.get_class_struct(), - GObject.ObjectClass.__info__) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + warnings.warn("test", PyGIWarning) + self.assertTrue(issubclass(warn[0].category, Warning)) + # We don't want PyGIWarning get ignored by default + self.assertFalse(issubclass(warn[0].category, ignored_by_default)) class TestDeprecation(unittest.TestCase): @@ -2950,3 +3170,156 @@ class TestDeprecation(unittest.TestCase): warnings.simplefilter('always') d.set_time(1) self.assertTrue(issubclass(warn[0].category, DeprecationWarning)) + self.assertEqual(str(warn[0].message), "GLib.Date.set_time is deprecated") + + def test_function(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + GLib.strcasecmp("foo", "bar") + self.assertTrue(issubclass(warn[0].category, DeprecationWarning)) + self.assertEqual(str(warn[0].message), "GLib.strcasecmp is deprecated") + + def test_deprecated_attribute_compat(self): + # test if the deprecation descriptor behaves like an instance attribute + + # save the descriptor + desc = type(GLib).__dict__["IO_STATUS_ERROR"] + + # the descriptor raises AttributeError for itself + self.assertFalse(hasattr(type(GLib), "IO_STATUS_ERROR")) + + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) + self.assertTrue(hasattr(GLib, "IO_STATUS_ERROR")) + + try: + # check if replacing works + GLib.IO_STATUS_ERROR = "foo" + self.assertEqual(GLib.IO_STATUS_ERROR, "foo") + finally: + # restore descriptor + try: + del GLib.IO_STATUS_ERROR + except AttributeError: + pass + setattr(type(GLib), "IO_STATUS_ERROR", desc) + + try: + # check if deleting works + del GLib.IO_STATUS_ERROR + self.assertFalse(hasattr(GLib, "IO_STATUS_ERROR")) + finally: + # restore descriptor + try: + del GLib.IO_STATUS_ERROR + except AttributeError: + pass + setattr(type(GLib), "IO_STATUS_ERROR", desc) + + def test_deprecated_attribute_warning(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + self.assertEqual(GLib.IO_STATUS_ERROR, GLib.IOStatus.ERROR) + GLib.IO_STATUS_ERROR + GLib.IO_STATUS_ERROR + self.assertEqual(len(warn), 3) + self.assertTrue( + issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex( + str(warn[0].message), + ".*GLib.IO_STATUS_ERROR.*GLib.IOStatus.ERROR.*") + + def test_deprecated_attribute_warning_coverage(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + GObject.markup_escape_text + GObject.PRIORITY_DEFAULT + GObject.GError + GObject.PARAM_CONSTRUCT + GObject.SIGNAL_ACTION + GObject.property + GObject.IO_STATUS_ERROR + GObject.G_MAXUINT64 + GLib.IO_STATUS_ERROR + GLib.SPAWN_SEARCH_PATH + GLib.OPTION_FLAG_HIDDEN + GLib.IO_FLAG_IS_WRITEABLE + GLib.IO_FLAG_NONBLOCK + GLib.USER_DIRECTORY_DESKTOP + GLib.OPTION_ERROR_BAD_VALUE + GLib.glib_version + GLib.pyglib_version + self.assertEqual(len(warn), 17) + + def test_deprecated_init_no_keywords(self): + def init(self, **kwargs): + self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3}) + + fn = gi.overrides.deprecated_init(init, arg_names=('a', 'b', 'c')) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + fn(self, 1, 2, 3) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*keyword.*a, b, c.*') + + def test_deprecated_init_no_keywords_out_of_order(self): + def init(self, **kwargs): + self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3}) + + fn = gi.overrides.deprecated_init(init, arg_names=('b', 'a', 'c')) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + fn(self, 2, 1, 3) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*keyword.*b, a, c.*') + + def test_deprecated_init_ignored_keyword(self): + def init(self, **kwargs): + self.assertDictEqual(kwargs, {'a': 1, 'c': 3}) + + fn = gi.overrides.deprecated_init(init, + arg_names=('a', 'b', 'c'), + ignore=('b',)) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + fn(self, 1, 2, 3) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*keyword.*a, b, c.*') + + def test_deprecated_init_with_aliases(self): + def init(self, **kwargs): + self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3}) + + fn = gi.overrides.deprecated_init(init, + arg_names=('a', 'b', 'c'), + deprecated_aliases={'b': 'bb', 'c': 'cc'}) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + + fn(self, a=1, bb=2, cc=3) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*keyword.*"bb, cc".*deprecated.*"b, c" respectively') + + def test_deprecated_init_with_defaults(self): + def init(self, **kwargs): + self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3}) + + fn = gi.overrides.deprecated_init(init, + arg_names=('a', 'b', 'c'), + deprecated_defaults={'b': 2, 'c': 3}) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + fn(self, a=1) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*relying on deprecated non-standard defaults.*' + 'explicitly use: b=2, c=3') diff --git a/tests/test_gio.py b/tests/test_gio.py index 57ab013..07ee506 100644 --- a/tests/test_gio.py +++ b/tests/test_gio.py @@ -1,11 +1,18 @@ # -*- Mode: Python; py-indent-offset: 4 -*- # vim: tabstop=4 shiftwidth=4 expandtab +import os import unittest +import warnings + +import pytest import gi.overrides +from gi import PyGIWarning from gi.repository import GLib, Gio +from .helper import ignore_gi_deprecation_warnings + class TestGio(unittest.TestCase): def test_file_enumerator(self): @@ -37,10 +44,19 @@ class TestGio(unittest.TestCase): value = menu.get_item_attribute_value(0, "action", GLib.VariantType.new("s")) self.assertEqual("app.test", value.unpack()) + def test_volume_monitor_warning(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + Gio.VolumeMonitor() + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIWarning)) + self.assertRegex(str(warn[0].message), + '.*Gio\\.VolumeMonitor\\.get\\(\\).*') + class TestGSettings(unittest.TestCase): def setUp(self): - self.settings = Gio.Settings('org.gnome.test') + self.settings = Gio.Settings.new('org.gnome.test') # we change the values in the tests, so set them to predictable start # value self.settings.reset('test-string') @@ -48,6 +64,17 @@ class TestGSettings(unittest.TestCase): self.settings.reset('test-boolean') self.settings.reset('test-enum') + def test_iter(self): + assert set(list(self.settings)) == set([ + 'test-tuple', 'test-array', 'test-boolean', 'test-string', + 'test-enum', 'test-range']) + + def test_get_set(self): + for key in self.settings: + old_value = self.settings[key] + self.settings[key] = old_value + assert self.settings[key] == old_value + def test_native(self): self.assertTrue('test-array' in self.settings.list_keys()) @@ -66,6 +93,9 @@ class TestGSettings(unittest.TestCase): v = self.settings.get_value('test-tuple') self.assertEqual(v.unpack(), (1, 2)) + v = self.settings.get_value('test-range') + assert v.unpack() == 123 + # set a value self.settings.set_string('test-string', 'World') self.assertEqual(self.settings.get_string('test-string'), 'World') @@ -78,12 +108,12 @@ class TestGSettings(unittest.TestCase): self.assertEqual(self.settings.get_property('path'), '/tests/') # optional constructor arguments - with_path = Gio.Settings('org.gnome.nopathtest', path='/mypath/') + with_path = Gio.Settings.new_with_path('org.gnome.nopathtest', '/mypath/') self.assertEqual(with_path.get_property('path'), '/mypath/') self.assertEqual(with_path['np-int'], 42) def test_dictionary_api(self): - self.assertEqual(len(self.settings), 5) + self.assertEqual(len(self.settings), 6) self.assertTrue('test-array' in self.settings) self.assertTrue('test-array' in self.settings.keys()) self.assertFalse('nonexisting' in self.settings) @@ -114,12 +144,25 @@ class TestGSettings(unittest.TestCase): self.assertRaises(ValueError, self.settings.__setitem__, 'test-enum', 'plum') self.assertRaises(KeyError, self.settings.__setitem__, 'unknown', 'moo') + def test_set_range(self): + self.settings['test-range'] = 7 + assert self.settings['test-range'] == 7 + self.settings['test-range'] = 65535 + assert self.settings['test-range'] == 65535 + + with pytest.raises(ValueError, match=".*7 - 65535.*"): + self.settings['test-range'] = 7 - 1 + + with pytest.raises(ValueError, match=".*7 - 65535.*"): + self.settings['test-range'] = 65535 + 1 + def test_empty(self): - empty = Gio.Settings('org.gnome.empty', path='/tests/') + empty = Gio.Settings.new_with_path('org.gnome.empty', '/tests/') self.assertEqual(len(empty), 0) self.assertEqual(bool(empty), True) self.assertEqual(empty.keys(), []) + @ignore_gi_deprecation_warnings def test_change_event(self): changed_log = [] change_event_log = [] @@ -139,6 +182,7 @@ class TestGSettings(unittest.TestCase): 1)]) +@unittest.skipIf(os.name == "nt", "FIXME") class TestGFile(unittest.TestCase): def setUp(self): self.file, self.io_stream = Gio.File.new_tmp('TestGFile.XXXXXX') @@ -201,3 +245,98 @@ class TestGFile(unittest.TestCase): main_loop = GLib.MainLoop() main_loop.run() self.assertFalse(self.file.query_exists(None)) + + +@unittest.skipIf(os.name == "nt", "crashes on Windows") +class TestGApplication(unittest.TestCase): + def test_command_line(self): + class App(Gio.Application): + args = None + + def __init__(self): + super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) + + def do_command_line(self, cmdline): + self.args = cmdline.get_arguments() + return 42 + + app = App() + res = app.run(['spam', 'eggs']) + + self.assertEqual(res, 42) + self.assertSequenceEqual(app.args, ['spam', 'eggs']) + + def test_local_command_line(self): + class App(Gio.Application): + local_args = None + + def __init__(self): + super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) + + def do_local_command_line(self, args): + self.local_args = args[:] # copy + args.remove('eggs') + + # True skips do_command_line being called. + return True, args, 42 + + app = App() + res = app.run(['spam', 'eggs']) + + self.assertEqual(res, 42) + self.assertSequenceEqual(app.local_args, ['spam', 'eggs']) + + def test_local_and_remote_command_line(self): + class App(Gio.Application): + args = None + local_args = None + + def __init__(self): + super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) + + def do_command_line(self, cmdline): + self.args = cmdline.get_arguments() + return 42 + + def do_local_command_line(self, args): + self.local_args = args[:] # copy + args.remove('eggs') + + # False causes do_command_line to be called with args. + return False, args, 0 + + app = App() + res = app.run(['spam', 'eggs']) + + self.assertEqual(res, 42) + self.assertSequenceEqual(app.args, ['spam']) + self.assertSequenceEqual(app.local_args, ['spam', 'eggs']) + + @unittest.skipUnless(hasattr(Gio.Application, 'add_main_option'), + 'Requires newer version of GLib') + def test_add_main_option(self): + stored_options = [] + + def on_handle_local_options(app, options): + stored_options.append(options) + return 0 # Return 0 if options have been handled + + def on_activate(app): + pass + + app = Gio.Application() + app.add_main_option(long_name='string', + short_name=b's', + flags=0, + arg=GLib.OptionArg.STRING, + description='some string') + + app.connect('activate', on_activate) + app.connect('handle-local-options', on_handle_local_options) + app.run(['app', '-s', 'test string']) + + self.assertEqual(len(stored_options), 1) + options = stored_options[0] + self.assertTrue(options.contains('string')) + self.assertEqual(options.lookup_value('string').unpack(), + 'test string') diff --git a/tests/test_glib.py b/tests/test_glib.py index 3cde168..19eff7f 100644 --- a/tests/test_glib.py +++ b/tests/test_glib.py @@ -1,31 +1,45 @@ # -*- Mode: Python -*- -# encoding: UTF-8 +import os +import sys import unittest import os.path import warnings import subprocess +import pytest from gi.repository import GLib from gi import PyGIDeprecationWarning -from compathelper import _unicode, _bytes - class TestGLib(unittest.TestCase): + + @pytest.mark.xfail() + def test_pytest_capture_error_in_closure(self): + # this test is supposed to fail + ml = GLib.MainLoop() + + def callback(): + ml.quit() + raise Exception("expected") + + GLib.idle_add(callback) + ml.run() + + @unittest.skipIf(os.name == "nt", "no bash on Windows") def test_find_program_in_path(self): bash_path = GLib.find_program_in_path('bash') - self.assertTrue(bash_path.endswith('/bash')) + self.assertTrue(bash_path.endswith(os.path.sep + 'bash')) self.assertTrue(os.path.exists(bash_path)) self.assertEqual(GLib.find_program_in_path('non existing'), None) def test_markup_escape_text(self): - self.assertEqual(GLib.markup_escape_text(_unicode('a&bä')), 'a&bä') - self.assertEqual(GLib.markup_escape_text(_bytes('a&b\x05')), 'a&b') + self.assertEqual(GLib.markup_escape_text(u'a&bä'), 'a&bä') + self.assertEqual(GLib.markup_escape_text(b'a&b\x05'), 'a&b') # with explicit length argument - self.assertEqual(GLib.markup_escape_text(_bytes('a\x05\x01\x02'), 2), 'a') + self.assertEqual(GLib.markup_escape_text(b'a\x05\x01\x02', 2), 'a') def test_progname(self): GLib.set_prgname('moo') @@ -37,15 +51,18 @@ class TestGLib(unittest.TestCase): def test_xdg_dirs(self): d = GLib.get_user_data_dir() - self.assertTrue('/' in d, d) - d = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DESKTOP) - self.assertTrue('/' in d, d) - # also works with backwards compatible enum names - self.assertEqual(GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC), - GLib.get_user_special_dir(GLib.USER_DIRECTORY_MUSIC)) + self.assertTrue(os.path.sep in d, d) + d = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP) + self.assertTrue(os.path.sep in d, d) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) + + # also works with backwards compatible enum names + self.assertEqual(GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC), + GLib.get_user_special_dir(GLib.USER_DIRECTORY_MUSIC)) for d in GLib.get_system_config_dirs(): - self.assertTrue('/' in d, d) + self.assertTrue(os.path.sep in d, d) for d in GLib.get_system_data_dirs(): self.assertTrue(isinstance(d, str), d) @@ -56,13 +73,26 @@ class TestGLib(unittest.TestCase): self.assertEqual(GLib.filename_display_name('foo'), 'foo') self.assertEqual(GLib.filename_display_basename('bar/foo'), 'foo') + def glibfsencode(f): + # the annotations of filename_from_utf8() was changed in + # https://bugzilla.gnome.org/show_bug.cgi?id=756128 + if isinstance(f, bytes): + return f + if os.name == "nt": + return f.encode("utf-8", "surrogatepass") + else: + return os.fsencode(f) + # this is locale dependent, so we cannot completely verify the result - res = GLib.filename_from_utf8(_unicode('aäb')) + res = GLib.filename_from_utf8(u'aäb') + res = glibfsencode(res) self.assertTrue(isinstance(res, bytes)) self.assertGreaterEqual(len(res), 3) # with explicit length argument - self.assertEqual(GLib.filename_from_utf8(_unicode('aäb'), 1), b'a') + res = GLib.filename_from_utf8(u'aäb', 1) + res = glibfsencode(res) + self.assertEqual(res, b'a') def test_uri_extract(self): res = GLib.uri_list_extract_uris('''# some comment @@ -82,6 +112,7 @@ https://my.org/q?x=1&y=2 self.assertTrue(isinstance(tm, float)) self.assertGreater(tm, 1350000000.0) + @unittest.skipIf(sys.platform == "darwin", "fails on OSX") def test_main_loop(self): # note we do not test run() here, as we use this in countless other # tests @@ -119,57 +150,96 @@ https://my.org/q?x=1&y=2 self.assertTrue(context.pending() in [True, False]) self.assertTrue(context.iteration(False) in [True, False]) + @unittest.skipIf(os.name == "nt", "hangs") def test_io_add_watch_no_data(self): (r, w) = os.pipe() call_data = [] def cb(fd, condition): call_data.append((fd, condition, os.read(fd, 1))) + if len(call_data) == 2: + ml.quit() return True # io_add_watch() takes an IOChannel, calling with an fd is deprecated with warnings.catch_warnings(record=True) as warn: warnings.simplefilter('always') - GLib.io_add_watch(r, GLib.IOCondition.IN, cb) + GLib.io_add_watch(r, GLib.IOCondition.IN, cb, + priority=GLib.PRIORITY_HIGH) self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + ml = GLib.MainLoop() - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(call_data, [(r, GLib.IOCondition.IN, b'a'), (r, GLib.IOCondition.IN, b'b')]) + @unittest.skipIf(os.name == "nt", "hangs") def test_io_add_watch_with_data(self): (r, w) = os.pipe() call_data = [] def cb(fd, condition, data): call_data.append((fd, condition, os.read(fd, 1), data)) + if len(call_data) == 2: + ml.quit() return True # io_add_watch() takes an IOChannel, calling with an fd is deprecated with warnings.catch_warnings(record=True) as warn: warnings.simplefilter('always') - GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo') + GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo', + priority=GLib.PRIORITY_HIGH) self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + ml = GLib.MainLoop() - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(call_data, [(r, GLib.IOCondition.IN, b'a', 'moo'), (r, GLib.IOCondition.IN, b'b', 'moo')]) + @unittest.skipIf(os.name == "nt", "hangs") + def test_io_add_watch_with_multiple_data(self): + (r, w) = os.pipe() + call_data = [] + + def cb(fd, condition, *user_data): + call_data.append((fd, condition, os.read(fd, 1), user_data)) + ml.quit() + return True + + # io_add_watch() takes an IOChannel, calling with an fd is deprecated + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo', 'foo') + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + + ml = GLib.MainLoop() + GLib.idle_add(lambda: os.write(w, b'a') and False) + GLib.timeout_add(2000, ml.quit) + ml.run() + + self.assertEqual(call_data, [(r, GLib.IOCondition.IN, b'a', ('moo', 'foo'))]) + + @unittest.skipIf(sys.platform == "darwin", "fails") + @unittest.skipIf(os.name == "nt", "no shell on Windows") def test_io_add_watch_pyfile(self): call_data = [] - cmd = subprocess.Popen('sleep 0.1; echo hello; sleep 0.2; echo world', - shell=True, stdout=subprocess.PIPE) + cmd = subprocess.Popen('echo hello; echo world', + shell=True, bufsize=0, stdout=subprocess.PIPE) def cb(file, condition): call_data.append((file, condition, file.readline())) @@ -194,13 +264,32 @@ https://my.org/q?x=1&y=2 (cmd.stdout, GLib.IOCondition.IN, b'world\n')]) def test_glib_version(self): - (major, minor, micro) = GLib.glib_version - self.assertGreaterEqual(major, 2) - self.assertGreaterEqual(minor, 0) - self.assertGreaterEqual(micro, 0) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) + + (major, minor, micro) = GLib.glib_version + self.assertGreaterEqual(major, 2) + self.assertGreaterEqual(minor, 0) + self.assertGreaterEqual(micro, 0) def test_pyglib_version(self): - (major, minor, micro) = GLib.pyglib_version - self.assertGreaterEqual(major, 3) - self.assertGreaterEqual(minor, 0) - self.assertGreaterEqual(micro, 0) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) + + (major, minor, micro) = GLib.pyglib_version + self.assertGreaterEqual(major, 3) + self.assertGreaterEqual(minor, 0) + self.assertGreaterEqual(micro, 0) + + def test_timezone_constructor(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + timezone = GLib.TimeZone("+05:21") + self.assertEqual(timezone.get_offset(0), ((5 * 60) + 21) * 60) + + def test_source_attach_implicit_context(self): + context = GLib.MainContext.default() + source = GLib.Idle() + source_id = source.attach() + self.assertEqual(context, source.get_context()) + self.assertTrue(GLib.Source.remove(source_id)) diff --git a/tests/test_gobject.py b/tests/test_gobject.py index 57d3822..fbc3bb7 100644 --- a/tests/test_gobject.py +++ b/tests/test_gobject.py @@ -4,24 +4,101 @@ import sys import gc import unittest import warnings +import weakref +import platform -from gi.repository import GObject, GLib +import pytest + +from gi.repository import GObject, GLib, Gio from gi import PyGIDeprecationWarning from gi.module import get_introspection_module -from gi._gobject import _gobject +from gi import _gi import testhelper +from .helper import capture_glib_deprecation_warnings + + +@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="crashes") +def test_gobject_weak_ref(): + + called = [] + + def callback(*args): + called.extend(args) + + # object gets finalized + obj = GObject.Object() + obj.weak_ref(callback, 1) + del obj + gc.collect() + gc.collect() + assert called == [1] + del called[:] + + # wrapper gets finalized first + obj = GObject.Object() + pyref = weakref.ref(obj, lambda x: callback(-2)) + value = GObject.Value(GObject.Object, obj) + ref = obj.weak_ref(callback, 2) + del obj + gc.collect() + assert called == [-2] + del pyref + value.unset() + gc.collect() + assert called == [-2, 2] + del called[:] + + # weakref gets unregistered first + obj = GObject.Object() + ref = obj.weak_ref(callback, 3) + ref.unref() + del obj + gc.collect() + assert not called + + # weakref gets GCed + obj = GObject.Object() + obj.weak_ref(callback, 4) + gc.collect() + del obj + assert called == [4] class TestGObjectAPI(unittest.TestCase): + + def test_run_dispose(self): + class TestObject(GObject.GObject): + int_prop = GObject.Property(default=0, type=int) + + obj = TestObject() + called = [] + + def on_notify(*args): + called.append(args) + + obj.connect('notify::int-prop', on_notify) + obj.notify("int-prop") + obj.notify("int-prop") + # after this everything should be disconnected + obj.run_dispose() + obj.notify("int-prop") + obj.notify("int-prop") + assert len(called) == 2 + + def test_call_method_uninitialized_instance(self): + obj = GObject.Object.__new__(GObject.Object) + with self.assertRaisesRegex(RuntimeError, '.*is not initialized'): + obj.notify("foo") + def test_gobject_inheritance(self): # GObject.Object is a class hierarchy as follows: # overrides.Object -> introspection.Object -> static.GObject GIObjectModule = get_introspection_module('GObject') self.assertTrue(issubclass(GObject.Object, GIObjectModule.Object)) - self.assertTrue(issubclass(GIObjectModule.Object, _gobject.GObject)) + self.assertTrue(issubclass(GIObjectModule.Object, _gi.GObject)) - self.assertEqual(_gobject.GObject.__gtype__, GObject.TYPE_OBJECT) + self.assertEqual(_gi.GObject.__gtype__, GObject.TYPE_OBJECT) self.assertEqual(GIObjectModule.Object.__gtype__, GObject.TYPE_OBJECT) self.assertEqual(GObject.Object.__gtype__, GObject.TYPE_OBJECT) @@ -58,17 +135,20 @@ class TestGObjectAPI(unittest.TestCase): self.assertLess(GObject.PRIORITY_HIGH, GObject.PRIORITY_DEFAULT) def test_min_max_int(self): - self.assertEqual(GObject.G_MAXINT16, 2 ** 15 - 1) - self.assertEqual(GObject.G_MININT16, -2 ** 15) - self.assertEqual(GObject.G_MAXUINT16, 2 ** 16 - 1) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) - self.assertEqual(GObject.G_MAXINT32, 2 ** 31 - 1) - self.assertEqual(GObject.G_MININT32, -2 ** 31) - self.assertEqual(GObject.G_MAXUINT32, 2 ** 32 - 1) + self.assertEqual(GObject.G_MAXINT16, 2 ** 15 - 1) + self.assertEqual(GObject.G_MININT16, -2 ** 15) + self.assertEqual(GObject.G_MAXUINT16, 2 ** 16 - 1) - self.assertEqual(GObject.G_MAXINT64, 2 ** 63 - 1) - self.assertEqual(GObject.G_MININT64, -2 ** 63) - self.assertEqual(GObject.G_MAXUINT64, 2 ** 64 - 1) + self.assertEqual(GObject.G_MAXINT32, 2 ** 31 - 1) + self.assertEqual(GObject.G_MININT32, -2 ** 31) + self.assertEqual(GObject.G_MAXUINT32, 2 ** 32 - 1) + + self.assertEqual(GObject.G_MAXINT64, 2 ** 63 - 1) + self.assertEqual(GObject.G_MININT64, -2 ** 63) + self.assertEqual(GObject.G_MAXUINT64, 2 ** 64 - 1) class TestReferenceCounting(unittest.TestCase): @@ -233,19 +313,23 @@ class TestPythonReferenceCounting(unittest.TestCase): def test_new_instance_has_two_refs(self): obj = GObject.GObject() - self.assertEqual(sys.getrefcount(obj), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(obj), 2) def test_new_instance_has_two_refs_using_gobject_new(self): obj = GObject.new(GObject.GObject) - self.assertEqual(sys.getrefcount(obj), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(obj), 2) def test_new_subclass_instance_has_two_refs(self): obj = A() - self.assertEqual(sys.getrefcount(obj), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(obj), 2) def test_new_subclass_instance_has_two_refs_using_gobject_new(self): obj = GObject.new(A) - self.assertEqual(sys.getrefcount(obj), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(obj), 2) class TestContextManagers(unittest.TestCase): @@ -270,14 +354,16 @@ class TestContextManagers(unittest.TestCase): self.assertEqual(self.tracking, [1, 2]) self.assertEqual(self.obj.__grefcount__, 1) - pyref_count = sys.getrefcount(self.obj) + if hasattr(sys, "getrefcount"): + pyref_count = sys.getrefcount(self.obj) # Using the context manager the tracking list should not be affected. # The GObject reference count should stay the same and the python # object ref-count should go up. with self.obj.freeze_notify(): self.assertEqual(self.obj.__grefcount__, 1) - self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1) self.obj.props.prop = 3 self.assertEqual(self.obj.props.prop, 3) self.assertEqual(self.tracking, [1, 2]) @@ -289,7 +375,8 @@ class TestContextManagers(unittest.TestCase): self.assertEqual(self.obj.props.prop, 3) self.assertEqual(self.tracking, [1, 2, 3]) self.assertEqual(self.obj.__grefcount__, 1) - self.assertEqual(sys.getrefcount(self.obj), pyref_count) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(self.obj), pyref_count) def test_handler_block_context(self): # Verify prop tracking list @@ -300,14 +387,16 @@ class TestContextManagers(unittest.TestCase): self.assertEqual(self.tracking, [1, 2]) self.assertEqual(self.obj.__grefcount__, 1) - pyref_count = sys.getrefcount(self.obj) + if hasattr(sys, "getrefcount"): + pyref_count = sys.getrefcount(self.obj) # Using the context manager the tracking list should not be affected. # The GObject reference count should stay the same and the python # object ref-count should go up. with self.obj.handler_block(self.handler): self.assertEqual(self.obj.__grefcount__, 1) - self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1) self.obj.props.prop = 3 self.assertEqual(self.obj.props.prop, 3) self.assertEqual(self.tracking, [1, 2]) @@ -319,7 +408,8 @@ class TestContextManagers(unittest.TestCase): self.assertEqual(self.obj.props.prop, 3) self.assertEqual(self.tracking, [1, 2]) self.assertEqual(self.obj.__grefcount__, 1) - self.assertEqual(sys.getrefcount(self.obj), pyref_count) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(self.obj), pyref_count) def test_freeze_notify_context_nested(self): self.assertEqual(self.tracking, []) @@ -415,6 +505,8 @@ class TestContextManagers(unittest.TestCase): self.assertEqual(self.tracking, [2]) +@unittest.skipUnless(hasattr(GObject.Binding, 'unbind'), + 'Requires newer GLib which has g_binding_unbind') class TestPropertyBindings(unittest.TestCase): class TestObject(GObject.GObject): int_prop = GObject.Property(default=0, type=int) @@ -438,6 +530,14 @@ class TestPropertyBindings(unittest.TestCase): self.assertEqual(self.source.int_prop, 1) self.assertEqual(self.target.int_prop, 2) + def test_call_binding(self): + binding = self.source.bind_property('int_prop', self.target, 'int_prop', + GObject.BindingFlags.DEFAULT) + with capture_glib_deprecation_warnings() as warn: + result = binding() + assert len(warn) + assert result is binding + def test_bidirectional_binding(self): binding = self.source.bind_property('int_prop', self.target, 'int_prop', GObject.BindingFlags.BIDIRECTIONAL) @@ -500,17 +600,19 @@ class TestPropertyBindings(unittest.TestCase): self.assertEqual(user_data, test_data) return value // 2 - test_data_ref_count = sys.getrefcount(test_data) - transform_to_ref_count = sys.getrefcount(transform_to) - transform_from_ref_count = sys.getrefcount(transform_from) + if hasattr(sys, "getrefcount"): + test_data_ref_count = sys.getrefcount(test_data) + transform_to_ref_count = sys.getrefcount(transform_to) + transform_from_ref_count = sys.getrefcount(transform_from) # bidirectional bindings binding = self.source.bind_property('int_prop', self.target, 'int_prop', GObject.BindingFlags.BIDIRECTIONAL, transform_to, transform_from, test_data) binding = binding # PyFlakes - binding_ref_count = sys.getrefcount(binding()) - binding_gref_count = binding().__grefcount__ + if hasattr(sys, "getrefcount"): + binding_ref_count = sys.getrefcount(binding) + binding_gref_count = binding.__grefcount__ self.source.int_prop = 1 self.assertEqual(self.source.int_prop, 1) @@ -520,19 +622,18 @@ class TestPropertyBindings(unittest.TestCase): self.assertEqual(self.source.int_prop, 2) self.assertEqual(self.target.int_prop, 4) - self.assertEqual(sys.getrefcount(binding()), binding_ref_count) - self.assertEqual(binding().__grefcount__, binding_gref_count) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(binding), binding_ref_count) + self.assertEqual(binding.__grefcount__, binding_gref_count) # test_data ref count increases by 2, once for each callback. - self.assertEqual(sys.getrefcount(test_data), test_data_ref_count + 2) - self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count + 1) - self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count + 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(test_data), test_data_ref_count + 2) + self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count + 1) + self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count + 1) # Unbind should clear out the binding and its transforms binding.unbind() - self.assertEqual(binding(), None) - del binding - gc.collect() # Setting source or target should not change the other. self.target.int_prop = 3 @@ -540,9 +641,10 @@ class TestPropertyBindings(unittest.TestCase): self.assertEqual(self.target.int_prop, 3) self.assertEqual(self.source.int_prop, 5) - self.assertEqual(sys.getrefcount(test_data), test_data_ref_count) - self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count) - self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(test_data), test_data_ref_count) + self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count) + self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count) def test_explicit_unbind_clears_connection(self): self.assertEqual(self.source.int_prop, 0) @@ -554,15 +656,24 @@ class TestPropertyBindings(unittest.TestCase): self.assertEqual(self.source.int_prop, 1) self.assertEqual(self.target.int_prop, 1) + # unbind should clear out the bindings self reference binding.unbind() - self.assertEqual(binding(), None) + self.assertEqual(binding.__grefcount__, 1) self.source.int_prop = 10 self.assertEqual(self.source.int_prop, 10) self.assertEqual(self.target.int_prop, 1) - # An already unbound BindingWeakRef will raise if unbind is attempted a second time. - self.assertRaises(ValueError, binding.unbind) + glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) + + # calling unbind() on an already unbound binding + if glib_version >= (2, 57, 3): + # Fixed in newer glib: + # https://gitlab.gnome.org/GNOME/glib/merge_requests/244 + for i in range(10): + binding.unbind() + else: + self.assertRaises(ValueError, binding.unbind) def test_reference_counts(self): self.assertEqual(self.source.__grefcount__, 1) @@ -572,7 +683,7 @@ class TestPropertyBindings(unittest.TestCase): # the act of binding and the ref incurred by using __call__ to generate # a wrapper from the weak binding ref within python. binding = self.source.bind_property('int_prop', self.target, 'int_prop') - self.assertEqual(binding().__grefcount__, 2) + self.assertEqual(binding.__grefcount__, 2) # Creating a binding does not inc refs on source and target (they are weak # on the binding object itself) @@ -581,18 +692,25 @@ class TestPropertyBindings(unittest.TestCase): # Use GObject.get_property because the "props" accessor leaks. # Note property names are canonicalized. - self.assertEqual(binding().get_property('source'), self.source) - self.assertEqual(binding().get_property('source_property'), 'int-prop') - self.assertEqual(binding().get_property('target'), self.target) - self.assertEqual(binding().get_property('target_property'), 'int-prop') - self.assertEqual(binding().get_property('flags'), GObject.BindingFlags.DEFAULT) - - # Delete reference to source or target and the binding should listen. + self.assertEqual(binding.get_property('source'), self.source) + self.assertEqual(binding.get_property('source_property'), 'int-prop') + self.assertEqual(binding.get_property('target'), self.target) + self.assertEqual(binding.get_property('target_property'), 'int-prop') + self.assertEqual(binding.get_property('flags'), GObject.BindingFlags.DEFAULT) + + # Delete reference to source or target and the binding will remove its own + # "self reference". ref = self.source.weak_ref() del self.source gc.collect() self.assertEqual(ref(), None) - self.assertEqual(binding(), None) + self.assertEqual(binding.__grefcount__, 1) + + # Finally clear out the last ref held by the python wrapper + ref = binding.weak_ref() + del binding + gc.collect() + self.assertEqual(ref(), None) class TestGValue(unittest.TestCase): @@ -657,5 +775,86 @@ class TestGValue(unittest.TestCase): value = GObject.Value(GObject.TYPE_OBJECT, obj) self.assertEqual(value.get_value(), obj) -if __name__ == '__main__': - unittest.main() + def test_value_array(self): + value = GObject.Value(GObject.ValueArray) + self.assertEqual(value.g_type, GObject.type_from_name('GValueArray')) + value.set_value([32, 'foo_bar', 0.3]) + self.assertEqual(value.get_value(), [32, 'foo_bar', 0.3]) + + def test_value_array_from_gvalue_list(self): + value = GObject.Value(GObject.ValueArray, [ + GObject.Value(GObject.TYPE_UINT, 0xffffffff), + GObject.Value(GObject.TYPE_STRING, 'foo_bar')]) + self.assertEqual(value.g_type, GObject.type_from_name('GValueArray')) + self.assertEqual(value.get_value(), [0xffffffff, 'foo_bar']) + self.assertEqual(testhelper.value_array_get_nth_type(value, 0), GObject.TYPE_UINT) + self.assertEqual(testhelper.value_array_get_nth_type(value, 1), GObject.TYPE_STRING) + + def test_value_array_append_gvalue(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + + arr = GObject.ValueArray.new(0) + arr.append(GObject.Value(GObject.TYPE_UINT, 0xffffffff)) + arr.append(GObject.Value(GObject.TYPE_STRING, 'foo_bar')) + self.assertEqual(arr.get_nth(0), 0xffffffff) + self.assertEqual(arr.get_nth(1), 'foo_bar') + self.assertEqual(testhelper.value_array_get_nth_type(arr, 0), GObject.TYPE_UINT) + self.assertEqual(testhelper.value_array_get_nth_type(arr, 1), GObject.TYPE_STRING) + + def test_gerror_boxing(self): + error = GLib.Error('test message', domain='mydomain', code=42) + value = GObject.Value(GLib.Error, error) + self.assertEqual(value.g_type, GObject.type_from_name('GError')) + + unboxed = value.get_value() + self.assertEqual(unboxed.message, error.message) + self.assertEqual(unboxed.domain, error.domain) + self.assertEqual(unboxed.code, error.code) + + def test_gerror_novalue(self): + GLib.Error('test message', domain='mydomain', code=42) + value = GObject.Value(GLib.Error) + self.assertEqual(value.g_type, GObject.type_from_name('GError')) + self.assertEqual(value.get_value(), None) + + +def test_list_properties(): + + def find_param(props, name): + for param in props: + if param.name == name: + return param + return + + list_props = GObject.list_properties + + props = list_props(Gio.Action) + param = find_param(props, "enabled") + assert param + assert param.value_type == GObject.TYPE_BOOLEAN + assert list_props("GAction") == list_props(Gio.Action) + assert list_props(Gio.Action.__gtype__) == list_props(Gio.Action) + + props = list_props(Gio.SimpleAction) + assert find_param(props, "enabled") + + def names(props): + return [p.name for p in props] + + assert (set(names(list_props(Gio.Action))) <= + set(names(list_props(Gio.SimpleAction)))) + + props = list_props(Gio.FileIcon) + param = find_param(props, "file") + assert param + assert param.value_type == Gio.File.__gtype__ + + assert list_props("GFileIcon") == list_props(Gio.FileIcon) + assert list_props(Gio.FileIcon.__gtype__) == list_props(Gio.FileIcon) + assert list_props(Gio.FileIcon( + file=Gio.File.new_for_path('.'))) == list_props(Gio.FileIcon) + + for obj in [Gio.ActionEntry, Gio.DBusError, 0, object()]: + with pytest.raises(TypeError): + list_props(obj) diff --git a/tests/test_gtk_template.py b/tests/test_gtk_template.py new file mode 100644 index 0000000..9dd7a90 --- /dev/null +++ b/tests/test_gtk_template.py @@ -0,0 +1,721 @@ +import tempfile +import os +import pytest + +Gtk = pytest.importorskip("gi.repository.Gtk") +GLib = pytest.importorskip("gi.repository.GLib") +GObject = pytest.importorskip("gi.repository.GObject") +Gio = pytest.importorskip("gi.repository.Gio") + +from .helper import capture_exceptions + +GTK4 = (Gtk._version == "4.0") + + +def new_gtype_name(_count=[0]): + _count[0] += 1 + return "GtkTemplateTest%d" % _count[0] + + +def ensure_resource_registered(): + resource_path = "/org/gnome/pygobject/test/a.ui" + + def is_registered(path): + try: + Gio.resources_get_info(path, Gio.ResourceLookupFlags.NONE) + except GLib.Error: + return False + return True + + if is_registered(resource_path): + return resource_path + + gresource_data = ( + b'GVariant\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00' + b'\xc8\x00\x00\x00\x00\x00\x00(\x06\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00' + b'\x06\x00\x00\x00KP\x90\x0b\x03\x00\x00\x00\xc8\x00\x00\x00' + b'\x04\x00L\x00\xcc\x00\x00\x00\xd0\x00\x00\x00\xb0\xb7$0' + b'\x00\x00\x00\x00\xd0\x00\x00\x00\x06\x00L\x00\xd8\x00\x00\x00' + b'\xdc\x00\x00\x00f\xc30\xd1\x01\x00\x00\x00\xdc\x00\x00\x00' + b'\n\x00L\x00\xe8\x00\x00\x00\xec\x00\x00\x00\xd4\xb5\x02\x00' + b'\xff\xff\xff\xff\xec\x00\x00\x00\x01\x00L\x00\xf0\x00\x00\x00' + b'\xf4\x00\x00\x005H}\xe3\x02\x00\x00\x00\xf4\x00\x00\x00' + b'\x05\x00L\x00\xfc\x00\x00\x00\x00\x01\x00\x00\xa2^\xd6t' + b'\x04\x00\x00\x00\x00\x01\x00\x00\x04\x00v\x00\x08\x01\x00\x00' + b'\xa5\x01\x00\x00org/\x01\x00\x00\x00gnome/\x00\x00\x02\x00\x00\x00' + b'pygobject/\x00\x00\x04\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00' + b'test/\x00\x00\x00\x05\x00\x00\x00a.ui\x00\x00\x00\x00' + b'\x8d\x00\x00\x00\x00\x00\x00\x00<interface>\n <template class="G' + b'tkTemplateTestResource" parent="GtkBox">\n <property name="spaci' + b'ng">42</property>\n </template>\n</interface>\n\x00\x00(uuay)' + ) + + resource = Gio.Resource.new_from_data(GLib.Bytes.new(gresource_data)) + Gio.resources_register(resource) + assert is_registered(resource_path) + return resource_path + + +def test_allow_init_template_call(): + + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + </template> +</interface> +""".format(type_name) + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + def __init__(self): + super(Foo, self).__init__() + self.init_template() + + # Stop current pygobject from handling the initialisation + del Foo.__dontuse_ginstance_init__ + + Foo() + + +def test_init_template_second_instance(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkLabel" id="label"> + </object> + </child> + </template> +</interface> +""".format(type_name) + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + label = Gtk.Template.Child("label") + + def __init__(self): + super(Foo, self).__init__() + self.init_template() + + # Stop current pygobject from handling the initialisation + del Foo.__dontuse_ginstance_init__ + + foo = Foo() + assert isinstance(foo.label, Gtk.Label) + + foo2 = Foo() + assert isinstance(foo2.label, Gtk.Label) + + +def test_main_example(): + + type_name = new_gtype_name() + + example_xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> + <property name="spacing">4</property> + <child> + <object class="GtkButton" id="hello_button"> + <property name="label">Hello World</property> + <signal name="clicked" handler="hello_button_clicked" + object="{0}" swapped="no"/> + <signal name="clicked" handler="hello_button_clicked_after" + object="{0}" swapped="no" after="yes"/> + </object> + </child> + <child> + <object class="GtkButton" id="goodbye_button"> + <property name="label">Goodbye World</property> + <signal name="clicked" handler="goodbye_button_clicked"/> + <signal name="clicked" handler="goodbye_button_clicked_after" + after="yes"/> + </object> + </child> + </template> +</interface> +""".format(type_name) + + @Gtk.Template.from_string(example_xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + def __init__(self): + super(Foo, self).__init__() + self.callback_hello = [] + self.callback_hello_after = [] + self.callback_goodbye = [] + self.callback_goodbye_after = [] + + @Gtk.Template.Callback("hello_button_clicked") + def _hello_button_clicked(self, *args): + self.callback_hello.append(args) + + @Gtk.Template.Callback("hello_button_clicked_after") + def _hello_after(self, *args): + self.callback_hello_after.append(args) + + _hello_button = Gtk.Template.Child("hello_button") + + goodbye_button = Gtk.Template.Child() + + @Gtk.Template.Callback("goodbye_button_clicked") + def _goodbye_button_clicked(self, *args): + self.callback_goodbye.append(args) + + @Gtk.Template.Callback("goodbye_button_clicked_after") + def _goodbye_after(self, *args): + self.callback_goodbye_after.append(args) + + w = Foo() + assert w.__gtype__.name == type_name + assert w.props.orientation == Gtk.Orientation.HORIZONTAL + assert w.props.spacing == 4 + assert isinstance(w._hello_button, Gtk.Button) + assert w._hello_button.props.label == "Hello World" + assert isinstance(w.goodbye_button, Gtk.Button) + assert w.goodbye_button.props.label == "Goodbye World" + + assert w.callback_hello == [] + w._hello_button.emit("clicked") + assert w.callback_hello == [(w,)] + assert w.callback_hello_after == [(w,)] + + assert w.callback_goodbye == [] + w.goodbye_button.emit("clicked") + assert w.callback_goodbye == [(w.goodbye_button,)] + assert w.callback_goodbye_after == [(w.goodbye_button,)] + + +def test_duplicate_handler(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkButton" id="hello_button"> + <signal name="clicked" handler="hello_button_clicked" /> + </object> + </child> + </template> +</interface> +""".format(type_name) + + class Foo(Gtk.Box): + __gtype_name__ = type_name + + @Gtk.Template.Callback("hello_button_clicked") + def _hello_button_clicked(self, *args): + pass + + @Gtk.Template.Callback() + def hello_button_clicked(self, *args): + pass + + with pytest.raises(RuntimeError, match=".*hello_button_clicked.*"): + Gtk.Template.from_string(xml)(Foo) + + +def test_duplicate_child(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkButton" id="hello_button" /> + </child> + </template> +</interface> +""".format(type_name) + + class Foo(Gtk.Box): + __gtype_name__ = type_name + + foo = Gtk.Template.Child("hello_button") + hello_button = Gtk.Template.Child() + + with pytest.raises(RuntimeError, match=".*hello_button.*"): + Gtk.Template.from_string(xml)(Foo) + + +def test_nonexist_handler(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + </template> +</interface> +""".format(type_name) + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + @Gtk.Template.Callback("nonexit") + def foo(self, *args): + pass + + with capture_exceptions() as exc_info: + Foo() + assert "nonexit" in str(exc_info[0].value) + assert exc_info[0].type is RuntimeError + + +def test_missing_handler_callback(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkButton" id="hello_button"> + <signal name="clicked" handler="i_am_not_used_in_python" /> + </object> + </child> + </template> +</interface> +""".format(type_name) + + class Foo(Gtk.Box): + __gtype_name__ = type_name + + Gtk.Template.from_string(xml)(Foo)() + + +def test_handler_swapped_not_supported(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkButton" id="hello_button"> + <signal name="clicked" handler="hello_button_clicked" + object="{0}" swapped="yes" /> + </object> + </child> + </template> +</interface> +""".format(type_name) + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + hello_button = Gtk.Template.Child() + + @Gtk.Template.Callback("hello_button_clicked") + def foo(self, *args): + pass + + with capture_exceptions() as exc_info: + Foo() + assert "G_CONNECT_SWAPPED" in str(exc_info[0].value) + + +def test_handler_class_staticmethod(): + type_name = new_gtype_name() + + xml = """\ +<interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkButton" id="hello_button"> + <signal name="clicked" handler="clicked_class" /> + <signal name="clicked" handler="clicked_static" /> + </object> + </child> + </template> +</interface> +""".format(type_name) + + signal_args_class = [] + signal_args_static = [] + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + hello_button = Gtk.Template.Child() + + @Gtk.Template.Callback("clicked_class") + @classmethod + def cb1(*args): + signal_args_class.append(args) + + @Gtk.Template.Callback("clicked_static") + @staticmethod + def cb2(*args): + signal_args_static.append(args) + + foo = Foo() + foo.hello_button.emit("clicked") + assert signal_args_class == [(Foo, foo.hello_button)] + assert signal_args_static == [(foo.hello_button,)] + + +@pytest.mark.skipif(Gtk._version == "4.0", reason="errors out first with gtk4") +def test_check_decorated_class(): + NonWidget = type("Foo", (object,), {}) + with pytest.raises(TypeError, match=".*on Widgets.*"): + Gtk.Template.from_string("")(NonWidget) + + Widget = type("Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()}) + with pytest.raises(TypeError, match=".*Cannot nest.*"): + Gtk.Template.from_string("")(Gtk.Template.from_string("")(Widget)) + + Widget = type("Foo", (Gtk.Widget,), {}) + with pytest.raises(TypeError, match=".*__gtype_name__.*"): + Gtk.Template.from_string("")(Widget) + + with pytest.raises(TypeError, match=".*on Widgets.*"): + Gtk.Template.from_string("")(object()) + + +@pytest.mark.skipif(Gtk._version == "4.0", reason="errors out first with gtk4") +def test_subclass_fail(): + @Gtk.Template.from_string("") + class Base(Gtk.Widget): + __gtype_name__ = new_gtype_name() + + with capture_exceptions() as exc_info: + type("Sub", (Base,), {})() + assert "not allowed at this time" in str(exc_info[0].value) + assert exc_info[0].type is TypeError + + +def test_from_file(): + fd, name = tempfile.mkstemp() + try: + os.close(fd) + + type_name = new_gtype_name() + + with open(name, "wb") as h: + h.write(u"""\ + <interface> + <template class="{0}" parent="GtkBox"> + <property name="spacing">42</property> + </template> + </interface> + """.format(type_name).encode()) + + @Gtk.Template.from_file(name) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + foo = Foo() + assert foo.props.spacing == 42 + finally: + os.remove(name) + + +def test_property_override(): + type_name = new_gtype_name() + + xml = """\ + <interface> + <template class="{0}" parent="GtkBox"> + <property name="spacing">42</property> + </template> + </interface> +""".format(type_name) + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + foo = Foo() + assert foo.props.spacing == 42 + + foo = Foo(spacing=124) + assert foo.props.spacing == 124 + + +def test_from_file_non_exist(): + dirname = tempfile.mkdtemp() + try: + path = os.path.join(dirname, "noexist") + + Widget = type( + "Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()}) + with pytest.raises(GLib.Error, match=".*No such file.*"): + Gtk.Template.from_file(path)(Widget) + finally: + os.rmdir(dirname) + + +def test_from_string_bytes(): + type_name = new_gtype_name() + + xml = u"""\ + <interface> + <template class="{0}" parent="GtkBox"> + <property name="spacing">42</property> + </template> + </interface> + """.format(type_name).encode() + + @Gtk.Template.from_string(xml) + class Foo(Gtk.Box): + __gtype_name__ = type_name + + foo = Foo() + assert foo.props.spacing == 42 + + +def test_from_resource(): + resource_path = ensure_resource_registered() + + @Gtk.Template.from_resource(resource_path) + class Foo(Gtk.Box): + __gtype_name__ = "GtkTemplateTestResource" + + foo = Foo() + assert foo.props.spacing == 42 + + +def test_from_resource_non_exit(): + Widget = type("Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()}) + with pytest.raises(GLib.Error, match=".*/or/gnome/pygobject/noexit.*"): + Gtk.Template.from_resource("/or/gnome/pygobject/noexit")(Widget) + + +def test_constructors(): + with pytest.raises(TypeError): + Gtk.Template() + + with pytest.raises(TypeError): + Gtk.Template(foo=1) + + Gtk.Template(filename="foo") + Gtk.Template(resource_path="foo") + Gtk.Template(string="foo") + + with pytest.raises(TypeError): + Gtk.Template(filename="foo", resource_path="bar") + + with pytest.raises(TypeError): + Gtk.Template(filename="foo", nope="bar") + + Gtk.Template.from_string("bla") + Gtk.Template.from_resource("foo") + Gtk.Template.from_file("foo") + + +def test_child_construct(): + Gtk.Template.Child() + Gtk.Template.Child("name") + with pytest.raises(TypeError): + Gtk.Template.Child("name", True) + Gtk.Template.Child("name", internal=True) + with pytest.raises(TypeError): + Gtk.Template.Child("name", internal=True, something=False) + + +def test_internal_child(): + + main_type_name = new_gtype_name() + + xml = """\ + <interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="GtkBox" id="somechild"> + <property name="margin-top">42</property> + </object> + </child> + </template> + </interface> + """.format(main_type_name) + + @Gtk.Template.from_string(xml) + class MainThing(Gtk.Box): + __gtype_name__ = main_type_name + + somechild = Gtk.Template.Child(internal=True) + + thing = MainThing() + assert thing.somechild.props.margin_top == 42 + + other_type_name = new_gtype_name() + + xml = """\ + <interface> + <template class="{0}" parent="GtkBox"> + <child> + <object class="{1}"> + <child internal-child="somechild"> + <object class="GtkBox"> + <property name="margin-top">24</property> + <child> + <object class="GtkLabel"> + <property name="label">foo</property> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + </interface> + """.format(other_type_name, main_type_name) + + @Gtk.Template.from_string(xml) + class OtherThing(Gtk.Box): + __gtype_name__ = other_type_name + + other = OtherThing() + if not GTK4: + child = other.get_children()[0] + else: + child = other.get_first_child() + + assert isinstance(child, MainThing) + + if not GTK4: + child = child.get_children()[0] + else: + child = child.get_first_child() + + assert isinstance(child, Gtk.Box) + assert child.props.margin_top == 24 + + if not GTK4: + child = child.get_children()[0] + else: + child = child.get_first_child() + + assert isinstance(child, Gtk.Label) + assert child.props.label == "foo" + + +def test_template_hierarchy(): + testlabel = """ + <interface> + <template class="TestLabel" parent="GtkLabel"> + </template> + </interface> + """ + + @Gtk.Template(string=testlabel) + class TestLabel(Gtk.Label): + + __gtype_name__ = "TestLabel" + + def __init__(self): + super().__init__() + self.props.label = "TestLabel" + + testbox = """ + <interface> + <template class="TestBox" parent="GtkBox"> + <child> + <object class="TestLabel" id="_testlabel"/> + </child> + </template> + </interface> + """ + + @Gtk.Template(string=testbox) + class TestBox(Gtk.Box): + + __gtype_name__ = "TestBox" + + _testlabel = Gtk.Template.Child() + + def __init__(self): + super().__init__() + + assert isinstance(self._testlabel, TestLabel) + + window = """ + <interface> + <template class="MyWindow" parent="GtkWindow"> + <property name="title">"Hellow World"</property> + <child> + <object class="TestBox" id="_testbox"> + <child> + <object class="TestLabel" id="_testlabel"/> + </child> + </object> + </child> + </template> + </interface> + """ + + @Gtk.Template(string=window) + class MyWindow(Gtk.Window): + + __gtype_name__ = "MyWindow" + + _testbox = Gtk.Template.Child() + _testlabel = Gtk.Template.Child() + + def __init__(self): + super().__init__() + + assert isinstance(self._testbox, TestBox) + assert isinstance(self._testlabel, TestLabel) + if not GTK4: + children = self._testbox.get_children() + else: + children = list(self._testbox) + + assert len(children) == 2 + + win = MyWindow() + assert isinstance(win, MyWindow) + + +def test_multiple_init_template_calls(): + xml = """ + <interface> + <template class="MyBox" parent="GtkBox"> + <child> + <object class="GtkLabel" id="_label"/> + </child> + </template> + </interface> + """ + + @Gtk.Template(string=xml) + class MyBox(Gtk.Box): + + __gtype_name__ = "MyBox" + + _label = Gtk.Template.Child() + + def __init__(self): + super().__init__() + self._label.props.label = "awesome label" + + my_box = MyBox() + assert isinstance(my_box, MyBox) + if not GTK4: + children = my_box.get_children() + else: + children = list(my_box) + + assert len(children) == 1 + my_box.init_template() + assert isinstance(my_box, MyBox) + if not GTK4: + children = my_box.get_children() + else: + children = list(my_box) + + assert len(children) == 1 diff --git a/tests/test_gtype.py b/tests/test_gtype.py index 8099101..1d24f9f 100644 --- a/tests/test_gtype.py +++ b/tests/test_gtype.py @@ -49,3 +49,64 @@ class TestTypeModuleLevelFunctions(unittest.TestCase): self.assertEqual(GObject.type_parent(CustomChild), CustomBase.__gtype__) self.assertEqual(GObject.type_parent(CustomBase), GObject.TYPE_OBJECT) self.assertRaises(RuntimeError, GObject.type_parent, GObject.GObject) + + +def test_gtype_has_value_table(): + assert CustomBase.__gtype__.has_value_table() + assert not GIMarshallingTests.Interface.__gtype__.has_value_table() + assert CustomChild.__gtype__.has_value_table() + + +def test_gtype_is_abstract(): + assert not CustomBase.__gtype__.is_abstract() + assert not GIMarshallingTests.Interface.__gtype__.is_abstract() + assert not CustomChild.__gtype__.is_abstract() + + +def test_gtype_is_classed(): + assert CustomBase.__gtype__.is_classed() + assert not GIMarshallingTests.Interface.__gtype__.is_classed() + assert CustomChild.__gtype__.is_classed() + + +def test_gtype_is_deep_derivable(): + assert CustomBase.__gtype__.is_deep_derivable() + assert not GIMarshallingTests.Interface.__gtype__.is_deep_derivable() + assert CustomChild.__gtype__.is_deep_derivable() + + +def test_gtype_is_derivable(): + assert CustomBase.__gtype__.is_derivable() + assert GIMarshallingTests.Interface.__gtype__.is_derivable() + assert CustomChild.__gtype__.is_derivable() + + +def test_gtype_is_value_abstract(): + assert not CustomBase.__gtype__.is_value_abstract() + assert not GIMarshallingTests.Interface.__gtype__.is_value_abstract() + assert not CustomChild.__gtype__.is_value_abstract() + + +def test_gtype_is_value_type(): + assert CustomBase.__gtype__.is_value_type() + assert not GIMarshallingTests.Interface.__gtype__.is_value_type() + assert CustomChild.__gtype__.is_value_type() + + +def test_gtype_children(): + assert CustomBase.__gtype__.children == [CustomChild.__gtype__] + assert GIMarshallingTests.Interface.__gtype__.children == [] + assert CustomChild.__gtype__.children == [] + + +def test_gtype_depth(): + assert CustomBase.__gtype__.depth == 2 + assert GIMarshallingTests.Interface.__gtype__.depth == 2 + assert CustomChild.__gtype__.depth == 3 + + +def test_gtype_interfaces(): + assert CustomBase.__gtype__.interfaces == [] + assert GIMarshallingTests.Interface.__gtype__.interfaces == [] + assert CustomChild.__gtype__.interfaces == \ + [GIMarshallingTests.Interface.__gtype__] diff --git a/tests/test_gtype_instance.py b/tests/test_gtype_instance.py new file mode 100644 index 0000000..a2f7eec --- /dev/null +++ b/tests/test_gtype_instance.py @@ -0,0 +1,8 @@ + +import pytest +from gi.repository import Regress + + +def test_fundamental_type_instantiation_fails(): + with pytest.raises(TypeError, match="No means to translate argument or return value for 'RegressTestFundamentalSubObject'"): + Regress.TestFundamentalSubObject.new("data") diff --git a/tests/test_import_machinery.py b/tests/test_import_machinery.py new file mode 100644 index 0000000..a8d5005 --- /dev/null +++ b/tests/test_import_machinery.py @@ -0,0 +1,154 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab + +import sys +import unittest + +import gi.overrides +import gi.module +import gi.importer + +from gi.repository import Regress + + +class TestOverrides(unittest.TestCase): + + def test_non_gi(self): + class MyClass: + pass + + try: + gi.overrides.override(MyClass) + self.fail('unexpected success of overriding non-GI class') + except TypeError as e: + self.assertTrue('Can not override a type MyClass' in str(e)) + + def test_separate_path(self): + # Regress override is in tests/gi/overrides, separate from gi/overrides + # https://bugzilla.gnome.org/show_bug.cgi?id=680913 + self.assertEqual(Regress.REGRESS_OVERRIDE, 42) + + def test_load_overrides(self): + mod = gi.module.get_introspection_module('GIMarshallingTests') + mod_override = gi.overrides.load_overrides(mod) + self.assertTrue(mod_override is not mod) + self.assertTrue(mod_override._introspection_module is mod) + self.assertEqual(mod_override.OVERRIDES_CONSTANT, 7) + self.assertEqual(mod.OVERRIDES_CONSTANT, 42) + + def test_load_no_overrides(self): + mod_key = "gi.overrides.GIMarshallingTests" + had_mod = mod_key in sys.modules + old_mod = sys.modules.get(mod_key) + try: + # this makes override import fail + sys.modules[mod_key] = None + mod = gi.module.get_introspection_module('GIMarshallingTests') + mod_override = gi.overrides.load_overrides(mod) + self.assertTrue(mod_override is mod) + finally: + del sys.modules[mod_key] + if had_mod: + sys.modules[mod_key] = old_mod + + +class TestModule(unittest.TestCase): + # Tests for gi.module + + def test_get_introspection_module_caching(self): + # This test attempts to minimize side effects by + # using a DynamicModule directly instead of going though: + # from gi.repository import Foo + + # Clear out introspection module cache before running this test. + old_modules = gi.module._introspection_modules + gi.module._introspection_modules = {} + + mod_name = 'GIMarshallingTests' + mod1 = gi.module.get_introspection_module(mod_name) + mod2 = gi.module.get_introspection_module(mod_name) + self.assertTrue(mod1 is mod2) + + # Restore the previous cache + gi.module._introspection_modules = old_modules + + def test_module_dependency_loading(self): + # Difficult to because this generally need to run in isolation to make + # sure GIMarshallingTests has not yet been loaded. But we can do this with: + # make check TEST_NAMES=test_import_machinery.TestModule.test_module_dependency_loading + if 'gi.repository.Gio' in sys.modules: + return + + from gi.repository import GIMarshallingTests + GIMarshallingTests # PyFlakes + + self.assertIn('gi.repository.Gio', sys.modules) + self.assertIn('gi.repository.GIMarshallingTests', sys.modules) + + def test_static_binding_protection(self): + # Importing old static bindings once gi has been imported should not + # crash but instead give back a dummy module which produces RuntimeErrors + # on access. + with self.assertRaises(AttributeError): + import gobject + gobject.anything + + with self.assertRaises(AttributeError): + import glib + glib.anything + + with self.assertRaises(AttributeError): + import gio + gio.anything + + with self.assertRaises(AttributeError): + import gtk + gtk.anything + + with self.assertRaises(AttributeError): + import gtk.gdk + gtk.gdk.anything + + +class TestImporter(unittest.TestCase): + def test_invalid_repository_module_name(self): + with self.assertRaises(ImportError) as context: + from gi.repository import InvalidGObjectRepositoryModuleName + InvalidGObjectRepositoryModuleName # pyflakes + + exception_string = str(context.exception) + + self.assertTrue('InvalidGObjectRepositoryModuleName' in exception_string) + self.assertTrue('introspection typelib' in exception_string) + + def test_require_version_warning(self): + check = gi.importer._check_require_version + + # make sure it doesn't fail at least + with check("GLib", 1): + from gi.repository import GLib + GLib + + # make sure the exception propagates + with self.assertRaises(ImportError): + with check("InvalidGObjectRepositoryModuleName", 1): + from gi.repository import InvalidGObjectRepositoryModuleName + InvalidGObjectRepositoryModuleName + + def test_require_version_versiontype(self): + import gi + with self.assertRaises(ValueError): + gi.require_version('GLib', 2.0) + + with self.assertRaises(ValueError): + gi.require_version('GLib', b'2.0') + + def test_require_versions(self): + import gi + gi.require_versions({'GLib': '2.0', 'Gio': '2.0', 'GObject': '2.0'}) + from gi.repository import GLib + GLib + + def test_get_import_stacklevel(self): + gi.importer.get_import_stacklevel(import_hook=True) + gi.importer.get_import_stacklevel(import_hook=False) diff --git a/tests/test_interface.py b/tests/test_interface.py index dd01af8..ba20cb4 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -20,6 +20,8 @@ class MyUnknown(Unknown, testhelper.Interface): def do_iface_method(self): self.called = True Unknown.do_iface_method(self) + + GObject.type_register(MyUnknown) @@ -32,6 +34,8 @@ class MyObject(GObject.GObject, testhelper.Interface): def do_iface_method(self): self.called = True + + GObject.type_register(MyObject) diff --git a/tests/test_internal_api.py b/tests/test_internal_api.py index ca50f6b..5136037 100644 --- a/tests/test_internal_api.py +++ b/tests/test_internal_api.py @@ -1,18 +1,44 @@ # -*- Mode: Python -*- import unittest +import pytest from gi.repository import GLib, GObject import testhelper -import testmodule + + +class PyGObject(GObject.GObject): + __gtype_name__ = 'PyGObject' + __gproperties__ = { + 'label': (GObject.TYPE_STRING, + 'label property', + 'the label of the object', + 'default', + GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), + } + + def __init__(self): + self._props = {} + GObject.GObject.__init__(self) + self.set_property('label', 'hello') + + def do_set_property(self, name, value): + self._props[name] = value + + def do_get_property(self, name): + return self._props[name] + + +def test_parse_constructor_args(): + assert testhelper.test_parse_constructor_args("foo") == 1 class TestObject(unittest.TestCase): def test_create_ctor(self): - o = testmodule.PyGObject() + o = PyGObject() self.assertTrue(isinstance(o, GObject.Object)) - self.assertTrue(isinstance(o, testmodule.PyGObject)) + self.assertTrue(isinstance(o, PyGObject)) # has expected property self.assertEqual(o.props.label, 'hello') @@ -22,7 +48,7 @@ class TestObject(unittest.TestCase): def test_pyobject_new_test_type(self): o = testhelper.create_test_type() - self.assertTrue(isinstance(o, testmodule.PyGObject)) + self.assertTrue(isinstance(o, PyGObject)) # has expected property self.assertEqual(o.props.label, 'hello') @@ -40,8 +66,8 @@ class TestGValueConversion(unittest.TestCase): self.assertEqual(testhelper.test_value(0), 0) self.assertEqual(testhelper.test_value(5), 5) self.assertEqual(testhelper.test_value(-5), -5) - self.assertEqual(testhelper.test_value(GObject.G_MAXINT32), GObject.G_MAXINT32) - self.assertEqual(testhelper.test_value(GObject.G_MININT32), GObject.G_MININT32) + self.assertEqual(testhelper.test_value(GLib.MAXINT32), GLib.MAXINT32) + self.assertEqual(testhelper.test_value(GLib.MININT32), GLib.MININT32) def test_str(self): self.assertEqual(testhelper.test_value('hello'), 'hello') @@ -69,5 +95,28 @@ class TestErrors(unittest.TestCase): self.assertEqual(testhelper.test_gerror_exception(callable_), None) -if __name__ == '__main__': - unittest.main() +def test_to_unichar_conv(): + assert testhelper.test_to_unichar_conv(u"A") == 65 + assert testhelper.test_to_unichar_conv(u"Ä") == 196 + + with pytest.raises(TypeError): + assert testhelper.test_to_unichar_conv(b"\x65") + + with pytest.raises(TypeError): + testhelper.test_to_unichar_conv(object()) + + with pytest.raises(TypeError): + testhelper.test_to_unichar_conv(u"AA") + + +def test_constant_strip_prefix(): + assert testhelper.constant_strip_prefix("foo", "bar") == "foo" + assert testhelper.constant_strip_prefix("foo", "f") == "oo" + assert testhelper.constant_strip_prefix("foo", "f") == "oo" + assert testhelper.constant_strip_prefix("ha2foo", "ha") == "a2foo" + assert testhelper.constant_strip_prefix("2foo", "ha") == "2foo" + assert testhelper.constant_strip_prefix("bla_foo", "bla") == "_foo" + + +def test_state_ensure_release(): + testhelper.test_state_ensure_release() diff --git a/tests/test_iochannel.py b/tests/test_iochannel.py index 0cc1b4b..c4c6e3d 100644 --- a/tests/test_iochannel.py +++ b/tests/test_iochannel.py @@ -1,19 +1,20 @@ # -*- Mode: Python -*- -# encoding: UTF-8 -from __future__ import unicode_literals +import os import unittest import tempfile import os.path -import fcntl import shutil import warnings +try: + import fcntl +except ImportError: + fcntl = None + from gi.repository import GLib from gi import PyGIDeprecationWarning -from compathelper import _unicode - class IOChannel(unittest.TestCase): def setUp(self): @@ -21,7 +22,7 @@ class IOChannel(unittest.TestCase): self.testutf8 = os.path.join(self.workdir, 'testutf8.txt') with open(self.testutf8, 'wb') as f: - f.write('''hello ♥ world + f.write(u'''hello ♥ world second line À demain!'''.encode('UTF-8')) @@ -42,24 +43,24 @@ second line ch = GLib.IOChannel(filename=self.testutf8) self.assertEqual(ch.get_encoding(), 'UTF-8') self.assertTrue(ch.get_close_on_unref()) - self.assertEqual(_unicode(ch.readline()), 'hello ♥ world\n') + self.assertEqual(ch.readline(), 'hello ♥ world\n') self.assertEqual(ch.get_buffer_condition(), GLib.IOCondition.IN) self.assertEqual(ch.readline(), 'second line\n') self.assertEqual(ch.readline(), '\n') - self.assertEqual(_unicode(ch.readline()), 'À demain!') + self.assertEqual(ch.readline(), 'À demain!') self.assertEqual(ch.get_buffer_condition(), 0) self.assertEqual(ch.readline(), '') - ch.close() + ch.shutdown(True) def test_file_readline_latin1(self): ch = GLib.IOChannel(filename=self.testlatin1, mode='r') ch.set_encoding('latin1') self.assertEqual(ch.get_encoding(), 'latin1') - self.assertEqual(_unicode(ch.readline()), 'hellø world\n') + self.assertEqual(ch.readline(), 'hellø world\n') self.assertEqual(ch.readline(), 'second line\n') self.assertEqual(ch.readline(), '\n') - self.assertEqual(_unicode(ch.readline()), 'À demain!') - ch.close() + self.assertEqual(ch.readline(), 'À demain!') + ch.shutdown(True) def test_file_iter(self): items = [] @@ -67,8 +68,8 @@ second line for item in ch: items.append(item) self.assertEqual(len(items), 4) - self.assertEqual(_unicode(items[0]), 'hello ♥ world\n') - ch.close() + self.assertEqual(items[0], 'hello ♥ world\n') + ch.shutdown(True) def test_file_readlines(self): ch = GLib.IOChannel(filename=self.testutf8) @@ -77,8 +78,8 @@ second line # empty one self.assertGreaterEqual(len(lines), 4) self.assertLessEqual(len(lines), 5) - self.assertEqual(_unicode(lines[0]), 'hello ♥ world\n') - self.assertEqual(_unicode(lines[3]), 'À demain!') + self.assertEqual(lines[0], 'hello ♥ world\n') + self.assertEqual(lines[3], 'À demain!') if len(lines) == 4: self.assertEqual(lines[4], '') @@ -108,28 +109,29 @@ second line ch.seek(2, 2) # SEEK_END # FIXME: does not work currently - #self.assertEqual(ch.read(2), b'n!') + # self.assertEqual(ch.read(2), b'n!') # invalid whence value self.assertRaises(ValueError, ch.seek, 0, 3) + ch.shutdown(True) def test_file_write(self): ch = GLib.IOChannel(filename=self.testout, mode='w') ch.set_encoding('latin1') ch.write('hellø world\n') - ch.close() + ch.shutdown(True) ch = GLib.IOChannel(filename=self.testout, mode='a') ch.set_encoding('latin1') ch.write('À demain!') - ch.close() + ch.shutdown(True) with open(self.testout, 'rb') as f: - self.assertEqual(f.read().decode('latin1'), 'hellø world\nÀ demain!') + self.assertEqual(f.read().decode('latin1'), u'hellø world\nÀ demain!') def test_file_writelines(self): ch = GLib.IOChannel(filename=self.testout, mode='w') ch.writelines(['foo', 'bar\n', 'baz\n', 'end']) - ch.close() + ch.shutdown(True) with open(self.testout, 'r') as f: self.assertEqual(f.read(), 'foobar\nbaz\nend') @@ -163,10 +165,11 @@ second line # closing flushes writer.set_buffered(True) writer.write('ghi') - writer.close() + writer.shutdown(True) self.assertEqual(reader.read(), b'ghi') - reader.close() + reader.shutdown(True) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_fd_read(self): (r, w) = os.pipe() @@ -184,8 +187,9 @@ second line os.close(w) self.assertEqual(ch.read(), b'\x03\x04') - ch.close() + ch.shutdown(True) + @unittest.skipUnless(fcntl, "no fcntl") def test_fd_write(self): (r, w) = os.pipe() fcntl.fcntl(r, fcntl.F_SETFL, fcntl.fcntl(r, fcntl.F_GETFL) | os.O_NONBLOCK) @@ -199,10 +203,11 @@ second line # now test blocking case, after closing the write end fcntl.fcntl(r, fcntl.F_SETFL, fcntl.fcntl(r, fcntl.F_GETFL) & ~os.O_NONBLOCK) ch.write(b'\x03\x04') - ch.close() + ch.shutdown(True) self.assertEqual(os.read(r, 10), b'\x03\x04') os.close(r) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_deprecated_method_add_watch_no_data(self): (r, w) = os.pipe() @@ -216,23 +221,28 @@ second line self.assertEqual(channel, ch) self.assertEqual(condition, GLib.IOCondition.IN) cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True # io_add_watch() method is deprecated, use GLib.io_add_watch with warnings.catch_warnings(record=True) as warn: warnings.simplefilter('always') - ch.add_watch(GLib.IOCondition.IN, cb) + ch.add_watch(GLib.IOCondition.IN, cb, priority=GLib.PRIORITY_HIGH) self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) - ml = GLib.MainLoop() + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + ml = GLib.MainLoop() + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_deprecated_method_add_watch_data_priority(self): (r, w) = os.pipe() @@ -247,6 +257,8 @@ second line self.assertEqual(condition, GLib.IOCondition.IN) self.assertEqual(data, 'hello') cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True ml = GLib.MainLoop() @@ -259,13 +271,17 @@ second line self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_add_watch_no_data(self): (r, w) = os.pipe() @@ -279,6 +295,8 @@ second line self.assertEqual(channel, ch) self.assertEqual(condition, GLib.IOCondition.IN) cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb) @@ -286,13 +304,18 @@ second line ml = GLib.MainLoop() self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_add_watch_with_data(self): (r, w) = os.pipe() @@ -307,6 +330,8 @@ second line self.assertEqual(condition, GLib.IOCondition.IN) self.assertEqual(data, 'hello') cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb, 'hello') @@ -314,13 +339,18 @@ second line ml = GLib.MainLoop() self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_add_watch_with_multi_data(self): (r, w) = os.pipe() @@ -337,6 +367,8 @@ second line self.assertEqual(data2, 'b') self.assertEqual(data3, 'c') cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb, @@ -345,13 +377,18 @@ second line ml = GLib.MainLoop() self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_deprecated_add_watch_no_data(self): (r, w) = os.pipe() @@ -365,6 +402,8 @@ second line self.assertEqual(channel, ch) self.assertEqual(condition, GLib.IOCondition.IN) cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True with warnings.catch_warnings(record=True) as warn: @@ -375,13 +414,18 @@ second line ml = GLib.MainLoop() self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + + GLib.idle_add(write) + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) + @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows") def test_deprecated_add_watch_with_data(self): (r, w) = os.pipe() @@ -396,6 +440,8 @@ second line self.assertEqual(condition, GLib.IOCondition.IN) self.assertEqual(data, 'hello') cb_reads.append(channel.read()) + if len(cb_reads) == 2: + ml.quit() return True with warnings.catch_warnings(record=True) as warn: @@ -407,18 +453,23 @@ second line ml = GLib.MainLoop() self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) - GLib.timeout_add(10, lambda: os.write(w, b'a') and False) - GLib.timeout_add(100, lambda: os.write(w, b'b') and False) - GLib.timeout_add(200, ml.quit) + + def write(): + os.write(w, b'a') + GLib.idle_add(lambda: os.write(w, b'b') and False) + + GLib.idle_add(write) + + GLib.timeout_add(2000, ml.quit) ml.run() self.assertEqual(cb_reads, [b'a', b'b']) def test_backwards_compat_flags(self): - self.assertEqual(GLib.IOCondition.IN, GLib.IO_IN) - self.assertEqual(GLib.IOFlags.NONBLOCK, GLib.IO_FLAG_NONBLOCK) - self.assertEqual(GLib.IOFlags.IS_SEEKABLE, GLib.IO_FLAG_IS_SEEKABLE) - self.assertEqual(GLib.IOStatus.NORMAL, GLib.IO_STATUS_NORMAL) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) -if __name__ == '__main__': - unittest.main() + self.assertEqual(GLib.IOCondition.IN, GLib.IO_IN) + self.assertEqual(GLib.IOFlags.NONBLOCK, GLib.IO_FLAG_NONBLOCK) + self.assertEqual(GLib.IOFlags.IS_SEEKABLE, GLib.IO_FLAG_IS_SEEKABLE) + self.assertEqual(GLib.IOStatus.NORMAL, GLib.IO_STATUS_NORMAL) diff --git a/tests/test_mainloop.py b/tests/test_mainloop.py index 44197b3..48399d1 100644 --- a/tests/test_mainloop.py +++ b/tests/test_mainloop.py @@ -1,24 +1,18 @@ # -*- Mode: Python -*- import os -import sys import select import signal -import time import unittest -try: - from _thread import start_new_thread - start_new_thread # pyflakes -except ImportError: - # Python 2 - from thread import start_new_thread from gi.repository import GLib -from compathelper import _bytes +from .helper import capture_exceptions class TestMainLoop(unittest.TestCase): + + @unittest.skipUnless(hasattr(os, "fork"), "no os.fork available") def test_exception_handling(self): pipe_r, pipe_w = os.pipe() @@ -37,56 +31,35 @@ class TestMainLoop(unittest.TestCase): GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, child_died, loop) os.close(pipe_r) - os.write(pipe_w, _bytes("Y")) + os.write(pipe_w, b"Y") os.close(pipe_w) - def excepthook(type, value, traceback): - self.assertTrue(type is Exception) - self.assertEqual(value.args[0], "deadbabe") - sys.excepthook = excepthook - try: - got_exception = False - try: - loop.run() - except: - got_exception = True - finally: - sys.excepthook = sys.__excepthook__ - - # - # The exception should be handled (by printing it) - # immediately on return from child_died() rather - # than here. See bug #303573 - # - self.assertFalse(got_exception) - - def test_concurrency(self): - def on_usr1(signum, frame): - pass + with capture_exceptions() as exc: + loop.run() - try: - # create a thread which will terminate upon SIGUSR1 by way of - # interrupting sleep() - orig_handler = signal.signal(signal.SIGUSR1, on_usr1) - start_new_thread(time.sleep, (10,)) - - # now create two main loops - loop1 = GLib.MainLoop() - loop2 = GLib.MainLoop() - GLib.timeout_add(100, lambda: os.kill(os.getpid(), signal.SIGUSR1)) - GLib.timeout_add(500, loop1.quit) - loop1.run() - loop2.quit() - finally: - signal.signal(signal.SIGUSR1, orig_handler) + assert len(exc) == 1 + assert exc[0].type is Exception + assert exc[0].value.args[0] == "deadbabe" + @unittest.skipUnless(hasattr(os, "fork"), "no os.fork available") + @unittest.skipIf(os.environ.get("PYGI_TEST_GDB"), "SIGINT stops gdb") def test_sigint(self): + r, w = os.pipe() pid = os.fork() if pid == 0: - time.sleep(0.5) + # wait for the parent process loop to start + os.read(r, 1) + os.close(r) + os.kill(os.getppid(), signal.SIGINT) os._exit(0) + def notify_child(): + # tell the child that it can kill the parent + os.write(w, b"X") + os.close(w) + + GLib.idle_add(notify_child) loop = GLib.MainLoop() try: loop.run() diff --git a/tests/test_object_marshaling.py b/tests/test_object_marshaling.py index 624ed9d..d52ff54 100644 --- a/tests/test_object_marshaling.py +++ b/tests/test_object_marshaling.py @@ -10,6 +10,11 @@ import warnings from gi.repository import GObject from gi.repository import GIMarshallingTests +try: + from gi.repository import Regress +except ImportError: + Regress = None + class StrongRef(object): # A class that behaves like weakref.ref but holds a strong reference. @@ -103,7 +108,8 @@ class TestVFuncsWithObjectArg(unittest.TestCase): vfuncs.get_ref_info_for_vfunc_return_object_transfer_full() # Use any vfunc to test this. gc.collect() - self.assertEqual(sys.getrefcount(vfuncs), 2) + if hasattr(sys, "getrefcount"): + self.assertEqual(sys.getrefcount(vfuncs), 2) self.assertEqual(vfuncs.__grefcount__, 1) del vfuncs @@ -120,11 +126,13 @@ class TestVFuncsWithObjectArg(unittest.TestCase): with warnings.catch_warnings(record=True) as warn: warnings.simplefilter('always') ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none() - self.assertTrue(issubclass(warn[0].category, RuntimeWarning)) + if hasattr(sys, "getrefcount"): + self.assertTrue(issubclass(warn[0].category, RuntimeWarning)) # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none) # should be a single floating ref - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertFalse(is_floating) gc.collect() @@ -136,9 +144,11 @@ class TestVFuncsWithObjectArg(unittest.TestCase): with warnings.catch_warnings(record=True) as warn: warnings.simplefilter('always') ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none() - self.assertTrue(issubclass(warn[0].category, RuntimeWarning)) + if hasattr(sys, "getrefcount"): + self.assertTrue(issubclass(warn[0].category, RuntimeWarning)) - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertFalse(is_floating) gc.collect() @@ -150,7 +160,8 @@ class TestVFuncsWithObjectArg(unittest.TestCase): # The vfunc caller receives full ownership of a single ref which should not # be floating. - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertFalse(is_floating) gc.collect() @@ -161,7 +172,8 @@ class TestVFuncsWithObjectArg(unittest.TestCase): vfuncs = self.VFuncs() ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full() - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertFalse(is_floating) gc.collect() @@ -175,9 +187,12 @@ class TestVFuncsWithObjectArg(unittest.TestCase): self.assertEqual(vfuncs.in_object_grefcount, 2) # initial + python wrapper self.assertFalse(vfuncs.in_object_is_floating) - self.assertEqual(ref_count, 1) # ensure python wrapper released + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) # ensure python wrapper released self.assertFalse(is_floating) + gc.collect() + gc.collect() self.assertTrue(vfuncs.object_ref() is None) def test_vfunc_in_object_transfer_full(self): @@ -191,9 +206,12 @@ class TestVFuncsWithObjectArg(unittest.TestCase): self.assertFalse(vfuncs.in_object_is_floating) # ensure python wrapper took ownership and released, after vfunc was complete - self.assertEqual(ref_count, 0) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 0) self.assertFalse(is_floating) + gc.collect() + gc.collect() self.assertTrue(vfuncs.object_ref() is None) @@ -205,6 +223,7 @@ class TestVFuncsWithFloatingArg(unittest.TestCase): Object = GObject.InitiallyUnowned ObjectRef = weakref.ref + @unittest.skipUnless(hasattr(sys, "getrefcount"), "refcount specific") def test_vfunc_return_object_transfer_none_with_floating(self): # Python is expected to return a single floating reference without warning. vfuncs = self.VFuncs() @@ -218,6 +237,7 @@ class TestVFuncsWithFloatingArg(unittest.TestCase): gc.collect() self.assertTrue(vfuncs.object_ref() is None) + @unittest.skipUnless(hasattr(sys, "getrefcount"), "refcount specific") def test_vfunc_out_object_transfer_none_with_floating(self): # Same as above except uses out arg instead of return vfuncs = self.VFuncs() @@ -234,7 +254,8 @@ class TestVFuncsWithFloatingArg(unittest.TestCase): ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full() # The vfunc caller receives full ownership of a single ref. - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertFalse(is_floating) gc.collect() @@ -245,7 +266,8 @@ class TestVFuncsWithFloatingArg(unittest.TestCase): vfuncs = self.VFuncs() ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full() - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertFalse(is_floating) gc.collect() @@ -262,9 +284,12 @@ class TestVFuncsWithFloatingArg(unittest.TestCase): self.assertTrue(vfuncs.in_object_is_floating) # vfunc caller should only have a single floating ref after the vfunc finishes - self.assertEqual(ref_count, 1) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 1) self.assertTrue(is_floating) + gc.collect() + gc.collect() self.assertTrue(vfuncs.object_ref() is None) def test_vfunc_in_object_transfer_full_with_floating(self): @@ -278,9 +303,12 @@ class TestVFuncsWithFloatingArg(unittest.TestCase): self.assertFalse(vfuncs.in_object_is_floating) # ensure python wrapper took ownership and released - self.assertEqual(ref_count, 0) + if hasattr(sys, "getrefcount"): + self.assertEqual(ref_count, 0) self.assertFalse(is_floating) + gc.collect() + gc.collect() self.assertTrue(vfuncs.object_ref() is None) @@ -535,69 +563,31 @@ class TestVFuncsWithHeldFloatingArg(unittest.TestCase): self.assertTrue(held_object_ref() is None) -class TestPropertyHoldingObject(unittest.TestCase): - def test_props_getter_holding_object_ref_count(self): - holder = GIMarshallingTests.PropertiesObject() - held = GObject.Object() - - self.assertEqual(holder.__grefcount__, 1) - self.assertEqual(held.__grefcount__, 1) - - holder.set_property('some-object', held) - self.assertEqual(holder.__grefcount__, 1) - - initial_ref_count = held.__grefcount__ - holder.props.some_object - gc.collect() - self.assertEqual(held.__grefcount__, initial_ref_count) - - def test_get_property_holding_object_ref_count(self): - holder = GIMarshallingTests.PropertiesObject() - held = GObject.Object() - - self.assertEqual(holder.__grefcount__, 1) - self.assertEqual(held.__grefcount__, 1) - - holder.set_property('some-object', held) - self.assertEqual(holder.__grefcount__, 1) - - initial_ref_count = held.__grefcount__ - holder.get_property('some-object') - gc.collect() - self.assertEqual(held.__grefcount__, initial_ref_count) - - def test_props_setter_holding_object_ref_count(self): - holder = GIMarshallingTests.PropertiesObject() - held = GObject.Object() - - self.assertEqual(holder.__grefcount__, 1) - self.assertEqual(held.__grefcount__, 1) - - # Setting property should only increase ref count by 1 - holder.props.some_object = held - self.assertEqual(holder.__grefcount__, 1) - self.assertEqual(held.__grefcount__, 2) - - # Clearing should pull it back down - holder.props.some_object = None - self.assertEqual(held.__grefcount__, 1) - - def test_set_property_holding_object_ref_count(self): - holder = GIMarshallingTests.PropertiesObject() - held = GObject.Object() - - self.assertEqual(holder.__grefcount__, 1) - self.assertEqual(held.__grefcount__, 1) - - # Setting property should only increase ref count by 1 - holder.set_property('some-object', held) - self.assertEqual(holder.__grefcount__, 1) - self.assertEqual(held.__grefcount__, 2) - - # Clearing should pull it back down - holder.set_property('some-object', None) - self.assertEqual(held.__grefcount__, 1) - - def test_set_object_property_to_invalid_type(self): - obj = GIMarshallingTests.PropertiesObject() - self.assertRaises(TypeError, obj.set_property, 'some-object', 'not_an_object') +@unittest.skipIf(Regress is None, 'Regress is required') +class TestArgumentTypeErrors(unittest.TestCase): + def test_object_argument_type_error(self): + # ensure TypeError is raised for things which are not GObjects + obj = Regress.TestObj() + obj.set_bare(GObject.Object()) + obj.set_bare(None) + + self.assertRaises(TypeError, obj.set_bare, object()) + self.assertRaises(TypeError, obj.set_bare, 42) + self.assertRaises(TypeError, obj.set_bare, 'not an object') + + def test_instance_argument_error(self): + # ensure TypeError is raised for non Regress.TestObj instances. + obj = Regress.TestObj() + self.assertEqual(Regress.TestObj.instance_method(obj), -1) + self.assertRaises(TypeError, Regress.TestObj.instance_method, object()) + self.assertRaises(TypeError, Regress.TestObj.instance_method, GObject.Object()) + self.assertRaises(TypeError, Regress.TestObj.instance_method, 42) + self.assertRaises(TypeError, Regress.TestObj.instance_method, 'not an object') + + def test_instance_argument_base_type_error(self): + # ensure TypeError is raised when a base type is passed to something + # expecting a derived type + obj = Regress.TestSubObj() + self.assertEqual(Regress.TestSubObj.instance_method(obj), 0) + self.assertRaises(TypeError, Regress.TestSubObj.instance_method, GObject.Object()) + self.assertRaises(TypeError, Regress.TestSubObj.instance_method, Regress.TestObj()) diff --git a/tests/test_option.py b/tests/test_option.py index 2900edd..1dc496a 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -1,20 +1,13 @@ #!/usr/bin/env python import unittest -import sys - -# py3k has StringIO in a different module -try: - from StringIO import StringIO - StringIO # pyflakes -except ImportError: - from io import StringIO from gi.repository import GLib +from .helper import capture_exceptions + class TestOption(unittest.TestCase): - EXCEPTION_MESSAGE = "This callback fails" def setUp(self): self.parser = GLib.option.OptionParser("NAMES...", @@ -28,7 +21,7 @@ class TestOption(unittest.TestCase): def _create_group(self): def option_callback(option, opt, value, parser): - raise Exception(self.EXCEPTION_MESSAGE) + raise Exception("foo") group = GLib.option.OptionGroup( "unittest", "Unit test options", "Show all unittest options", @@ -55,29 +48,53 @@ class TestOption(unittest.TestCase): self.parser.add_option_group(group) return group - def test_parse_args(self): + def test_integer(self): + self._create_group() options, args = self.parser.parse_args( - ["test_option.py"]) - self.assertFalse(args) + ["--test-integer", "42", "bla"]) + assert options.test_integer == 42 + assert args == ["bla"] + + def test_file(self): + self._create_group() options, args = self.parser.parse_args( - ["test_option.py", "foo"]) - self.assertEqual(args, []) + ["--file", "fn", "bla"]) + assert options.unit_file == "fn" + assert args == ["bla"] + + def test_mixed(self): + self._create_group() options, args = self.parser.parse_args( - ["test_option.py", "foo", "bar"]) - self.assertEqual(args, []) + ["--file", "fn", "--test-integer", "12", "--test", + "--g-fatal-warnings", "nope"]) + + assert options.unit_file == "fn" + assert options.test_integer == 12 + assert options.test is False + assert options.fatal_warnings is True + assert args == ["nope"] + + def test_parse_args(self): + options, args = self.parser.parse_args([]) + self.assertFalse(args) + + options, args = self.parser.parse_args(["foo"]) + self.assertEqual(args, ["foo"]) + + options, args = self.parser.parse_args(["foo", "bar"]) + self.assertEqual(args, ["foo", "bar"]) def test_parse_args_double_dash(self): - options, args = self.parser.parse_args( - ["test_option.py", "--", "-xxx"]) - #self.assertEqual(args, ["-xxx"]) + options, args = self.parser.parse_args(["--", "-xxx"]) + self.assertEqual(args, ["--", "-xxx"]) def test_parse_args_group(self): group = self._create_group() options, args = self.parser.parse_args( - ["test_option.py", "--test", "-f", "test"]) + ["--test", "-f", "test"]) self.assertFalse(options.test) self.assertEqual(options.unit_file, "test") @@ -90,26 +107,21 @@ class TestOption(unittest.TestCase): def test_option_value_error(self): self._create_group() self.assertRaises(GLib.option.OptionValueError, self.parser.parse_args, - ["test_option.py", "--test-integer=text"]) + ["--test-integer=text"]) def test_bad_option_error(self): self.assertRaises(GLib.option.BadOptionError, self.parser.parse_args, - ["test_option.py", "--unknwon-option"]) + ["--unknwon-option"]) def test_option_group_constructor(self): self.assertRaises(TypeError, GLib.option.OptionGroup) def test_standard_error(self): self._create_group() - sio = StringIO() - old_stderr = sys.stderr - sys.stderr = sio - try: - self.parser.parse_args( - ["test_option.py", "--callback-failure-test"]) - finally: - sys.stderr = old_stderr - - assert (sio.getvalue().split('\n')[-2] == - "Exception: " + self.EXCEPTION_MESSAGE) + + with capture_exceptions() as exc: + self.parser.parse_args(["--callback-failure-test"]) + + assert len(exc) == 1 + assert exc[0].value.args[0] == "foo" diff --git a/tests/test_ossig.py b/tests/test_ossig.py new file mode 100644 index 0000000..bdd7f70 --- /dev/null +++ b/tests/test_ossig.py @@ -0,0 +1,182 @@ +# Copyright 2017 Christoph Reiter +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import + +import os +import signal +import unittest +import threading +from contextlib import contextmanager + +try: + from gi.repository import Gtk + Gtk_version = Gtk._version +except ImportError: + Gtk = None + Gtk_version = None +from gi.repository import Gio, GLib +from gi._ossighelper import wakeup_on_signal, register_sigint_fallback + + +class TestOverridesWakeupOnAlarm(unittest.TestCase): + + @contextmanager + def _run_with_timeout(self, timeout, abort_func): + failed = [] + + def fail(): + abort_func() + failed.append(1) + return True + + fail_id = GLib.timeout_add(timeout, fail) + try: + yield + finally: + GLib.source_remove(fail_id) + self.assertFalse(failed) + + def test_basic(self): + self.assertEqual(signal.set_wakeup_fd(-1), -1) + with wakeup_on_signal(): + pass + self.assertEqual(signal.set_wakeup_fd(-1), -1) + + def test_in_thread(self): + failed = [] + + def target(): + try: + with wakeup_on_signal(): + pass + except: + failed.append(1) + + t = threading.Thread(target=target) + t.start() + t.join(5) + self.assertFalse(failed) + + @unittest.skipIf(os.name == "nt", "not on Windows") + def test_glib_mainloop(self): + loop = GLib.MainLoop() + signal.signal(signal.SIGALRM, lambda *args: loop.quit()) + GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001) + + with self._run_with_timeout(2000, loop.quit): + loop.run() + + @unittest.skipIf(os.name == "nt", "not on Windows") + def test_gio_application(self): + app = Gio.Application() + signal.signal(signal.SIGALRM, lambda *args: app.quit()) + GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001) + + with self._run_with_timeout(2000, app.quit): + app.hold() + app.connect("activate", lambda *args: None) + app.run() + + @unittest.skipIf(Gtk is None or os.name == "nt", "not on Windows") + @unittest.skipIf(Gtk is None or Gtk_version == "4.0", "not in gtk4") + def test_gtk_main(self): + signal.signal(signal.SIGALRM, lambda *args: Gtk.main_quit()) + GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001) + + with self._run_with_timeout(2000, Gtk.main_quit): + Gtk.main() + + @unittest.skipIf(Gtk is None or os.name == "nt", "not on Windows") + @unittest.skipIf(Gtk is None or Gtk_version == "4.0", "not in gtk4") + def test_gtk_dialog_run(self): + w = Gtk.Window() + d = Gtk.Dialog(transient_for=w) + signal.signal(signal.SIGALRM, lambda *args: d.destroy()) + GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001) + + with self._run_with_timeout(2000, d.destroy): + d.run() + + +class TestSigintFallback(unittest.TestCase): + + def setUp(self): + self.assertEqual( + signal.getsignal(signal.SIGINT), signal.default_int_handler) + + def tearDown(self): + self.assertEqual( + signal.getsignal(signal.SIGINT), signal.default_int_handler) + + def test_replace_handler_and_restore_nested(self): + with register_sigint_fallback(lambda: None): + new_handler = signal.getsignal(signal.SIGINT) + self.assertNotEqual(new_handler, signal.default_int_handler) + with register_sigint_fallback(lambda: None): + self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler) + self.assertEqual( + signal.getsignal(signal.SIGINT), signal.default_int_handler) + + def test_no_replace_if_not_default(self): + new_handler = lambda *args: None + signal.signal(signal.SIGINT, new_handler) + try: + with register_sigint_fallback(lambda: None): + self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler) + with register_sigint_fallback(lambda: None): + self.assertTrue( + signal.getsignal(signal.SIGINT) is new_handler) + self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler) + finally: + signal.signal(signal.SIGINT, signal.default_int_handler) + + def test_noop_in_threads(self): + failed = [] + + def target(): + try: + with register_sigint_fallback(lambda: None): + with register_sigint_fallback(lambda: None): + self.assertTrue( + signal.getsignal(signal.SIGINT) is + signal.default_int_handler) + except: + failed.append(1) + + t = threading.Thread(target=target) + t.start() + t.join(5) + self.assertFalse(failed) + + @unittest.skipIf(os.name == "nt", "not on Windows") + def test_no_replace_if_set_by_glib(self): + id_ = GLib.unix_signal_add( + GLib.PRIORITY_DEFAULT, signal.SIGINT, lambda *args: None) + try: + # signal.getsignal() doesn't pick up that unix_signal_add() + # has changed the handler, but we should anyway. + self.assertEqual( + signal.getsignal(signal.SIGINT), signal.default_int_handler) + with register_sigint_fallback(lambda: None): + self.assertEqual( + signal.getsignal(signal.SIGINT), + signal.default_int_handler) + self.assertEqual( + signal.getsignal(signal.SIGINT), signal.default_int_handler) + finally: + GLib.source_remove(id_) + signal.signal(signal.SIGINT, signal.SIG_DFL) + signal.signal(signal.SIGINT, signal.default_int_handler) diff --git a/tests/test_overrides.py b/tests/test_overrides.py deleted file mode 100644 index e1af1f1..0000000 --- a/tests/test_overrides.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- Mode: Python; py-indent-offset: 4 -*- -# vim: tabstop=4 shiftwidth=4 expandtab - -import unittest - -import gi.overrides -import gi.module - -try: - from gi.repository import Regress - Regress # pyflakes -except ImportError: - Regress = None - - -class TestRegistry(unittest.TestCase): - def test_non_gi(self): - class MyClass: - pass - - try: - gi.overrides.override(MyClass) - self.fail('unexpected success of overriding non-GI class') - except TypeError as e: - self.assertTrue('Can not override a type MyClass' in str(e)) - - @unittest.skipUnless(Regress, 'built without cairo support') - def test_separate_path(self): - # Regress override is in tests/gi/overrides, separate from gi/overrides - # https://bugzilla.gnome.org/show_bug.cgi?id=680913 - self.assertEqual(Regress.REGRESS_OVERRIDE, 42) - - -class TestModule(unittest.TestCase): - # Tests for gi.module - - def test_get_introspection_module_caching(self): - # This test attempts to minimize side effects by - # using a DynamicModule directly instead of going though: - # from gi.repository import Foo - - # Clear out introspection module cache before running this test. - old_modules = gi.module._introspection_modules - gi.module._introspection_modules = {} - - mod_name = 'GIMarshallingTests' - mod1 = gi.module.get_introspection_module(mod_name) - mod2 = gi.module.get_introspection_module(mod_name) - self.assertTrue(mod1 is mod2) - - # Using a DynamicModule will use get_introspection_module internally - # in its _load method. - mod_overridden = gi.module.DynamicModule(mod_name) - mod_overridden._load() - self.assertTrue(mod1 is mod_overridden._introspection_module) - - # Restore the previous cache - gi.module._introspection_modules = old_modules diff --git a/tests/test_overrides_gdk.py b/tests/test_overrides_gdk.py index 46f0a38..119e56b 100644 --- a/tests/test_overrides_gdk.py +++ b/tests/test_overrides_gdk.py @@ -1,19 +1,46 @@ # -*- Mode: Python; py-indent-offset: 4 -*- # vim: tabstop=4 shiftwidth=4 expandtab +import re +import os +import sys import unittest +import pytest +import gi import gi.overrides +from gi import PyGIDeprecationWarning try: - from gi.repository import Gdk, GdkPixbuf, Gtk - Gdk # pyflakes + from gi.repository import Gio, Gdk, GdkPixbuf, Gtk + GDK4 = Gdk._version == "4.0" except ImportError: Gdk = None + GDK4 = False + + +def gtkver(): + if Gtk is None: + return (0, 0, 0) + return (Gtk.get_major_version(), + Gtk.get_minor_version(), + Gtk.get_micro_version()) + + +try: + gi.require_foreign('cairo') + has_cairo = True +except ImportError: + has_cairo = False + +from .helper import capture_glib_deprecation_warnings @unittest.skipUnless(Gdk, 'Gdk not available') class TestGdk(unittest.TestCase): + + @unittest.skipIf(sys.platform == "darwin" or os.name == "nt", "crashes") + @unittest.skipIf(GDK4, "not in gdk4") def test_constructor(self): attribute = Gdk.WindowAttr() attribute.window_type = Gdk.WindowType.CHILD @@ -22,14 +49,21 @@ class TestGdk(unittest.TestCase): window = Gdk.Window(None, attribute, attributes_mask) self.assertEqual(window.get_window_type(), Gdk.WindowType.CHILD) + @unittest.skipIf(GDK4, "not in gdk4") def test_color(self): color = Gdk.Color(100, 200, 300) self.assertEqual(color.red, 100) self.assertEqual(color.green, 200) self.assertEqual(color.blue, 300) - self.assertEqual(color, Gdk.Color(100, 200, 300)) + with capture_glib_deprecation_warnings(): + self.assertEqual(color, Gdk.Color(100, 200, 300)) self.assertNotEqual(color, Gdk.Color(1, 2, 3)) + self.assertNotEqual(color, None) + # assertNotEqual only tests __ne__. Following line explicitly + # tests __eq__ with objects of other types + self.assertFalse(color == object()) + @unittest.skipIf(GDK4, "not in gdk4") def test_color_floats(self): self.assertEqual(Gdk.Color(13107, 21845, 65535), Gdk.Color.from_floats(0.2, 1.0 / 3.0, 1.0)) @@ -43,6 +77,20 @@ class TestGdk(unittest.TestCase): self.assertEqual(Gdk.RGBA.from_color(Gdk.Color(13107, 21845, 65535)), Gdk.RGBA(0.2, 1.0 / 3.0, 1.0, 1.0)) + @unittest.skipIf(GDK4, "not in gdk4") + def test_color_to_floats_attrs(self): + color = Gdk.Color(13107, 21845, 65535) + assert color.red_float == 0.2 + color.red_float = 0 + assert color.red_float == 0 + assert color.green_float == 1.0 / 3.0 + color.green_float = 0 + assert color.green_float == 0 + assert color.blue_float == 1.0 + color.blue_float = 0 + assert color.blue_float == 0 + + @unittest.skipIf(GDK4, "not in gdk4") def test_rgba(self): self.assertEqual(Gdk.RGBA, gi.overrides.Gdk.RGBA) rgba = Gdk.RGBA(0.1, 0.2, 0.3, 0.4) @@ -54,25 +102,98 @@ class TestGdk(unittest.TestCase): self.assertEqual(rgba.alpha, 0.4) rgba.green = 0.9 self.assertEqual(rgba.green, 0.9) + self.assertNotEqual(rgba, None) + # assertNotEqual only tests __ne__. Following line explicitly + # tests __eq__ with objects of other types + self.assertFalse(rgba == object()) # Iterator/tuple convsersion self.assertEqual(tuple(Gdk.RGBA(0.1, 0.2, 0.3, 0.4)), (0.1, 0.2, 0.3, 0.4)) + @unittest.skipUnless(GDK4, "only in gdk4") + def test_rgba_gtk4(self): + c = Gdk.RGBA() + assert c.to_string() == "rgba(0,0,0,0)" + + @unittest.skipIf(not has_cairo or GDK4, "not in gdk4") + def test_window(self): + w = Gtk.Window() + w.realize() + window = w.get_window() + with capture_glib_deprecation_warnings(): + assert window.cairo_create() is not None + + @unittest.skipIf(GDK4, "not in gdk4") + def test_drag_context(self): + context = Gdk.DragContext() + # using it this way crashes.. + assert hasattr(context, "finish") + + @unittest.skipIf(GDK4, "not in gdk4") def test_event(self): event = Gdk.Event.new(Gdk.EventType.CONFIGURE) self.assertEqual(event.type, Gdk.EventType.CONFIGURE) self.assertEqual(event.send_event, 0) + event = Gdk.Event() + event.type = Gdk.EventType.SCROLL + self.assertRaises(AttributeError, lambda: getattr(event, 'foo_bar')) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_scroll_event(self): + event = Gdk.Event.new(Gdk.EventType.SCROLL) + assert event.direction == Gdk.ScrollDirection.UP + + @unittest.skipIf(GDK4, "not in gdk4") + def test_event_strip_boolean(self): + ev = Gdk.EventButton() + ev.type = Gdk.EventType.BUTTON_PRESS + assert ev.get_coords() == (0.0, 0.0) + + # https://gitlab.gnome.org/GNOME/pygobject/issues/85 + ev = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS) + assert ev.get_coords() == (True, 0.0, 0.0) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_event_touch(self): + event = Gdk.Event.new(Gdk.EventType.TOUCH_BEGIN) + self.assertEqual(event.type, Gdk.EventType.TOUCH_BEGIN) + + # emulating_pointer is unique to touch events + self.assertFalse(event.emulating_pointer) + self.assertFalse(event.touch.emulating_pointer) + + event.emulating_pointer = True + self.assertTrue(event.emulating_pointer) + self.assertTrue(event.touch.emulating_pointer) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_event_setattr(self): event = Gdk.Event.new(Gdk.EventType.DRAG_MOTION) event.x_root, event.y_root = 0, 5 + self.assertEqual(event.dnd.x_root, 0) + self.assertEqual(event.dnd.y_root, 5) self.assertEqual(event.x_root, 0) self.assertEqual(event.y_root, 5) - event = Gdk.Event() - event.type = Gdk.EventType.SCROLL - self.assertRaises(AttributeError, lambda: getattr(event, 'foo_bar')) + # this used to work, keep it that way + self.assertFalse(hasattr(event, "foo_bar")) + event.foo_bar = 42 + + # unhandled type + event.type = Gdk.EventType.EVENT_LAST + with pytest.raises(AttributeError): + event.foo_bar + event.foo_bar = 42 + assert event.foo_bar == 42 + + @unittest.skipIf(GDK4, "not in gdk4") + def test_event_repr(self): + event = Gdk.Event.new(Gdk.EventType.CONFIGURE) + self.assertTrue("CONFIGURE" in repr(event)) + @unittest.skipIf(GDK4, "not in gdk4") def test_event_structures(self): def button_press_cb(button, event): self.assertTrue(isinstance(event, Gdk.EventButton)) @@ -88,18 +209,22 @@ class TestGdk(unittest.TestCase): b = Gtk.Button() b.connect('button-press-event', button_press_cb) w.add(b) - w.show_all() + b.show() + b.realize() Gdk.test_simulate_button(b.get_window(), 2, 5, 0, Gdk.ModifierType.CONTROL_MASK, Gdk.EventType.BUTTON_PRESS) + @unittest.skipIf(GDK4, "not in gdk4") def test_cursor(self): self.assertEqual(Gdk.Cursor, gi.overrides.Gdk.Cursor) - c = Gdk.Cursor(Gdk.CursorType.WATCH) + with capture_glib_deprecation_warnings(): + c = Gdk.Cursor(Gdk.CursorType.WATCH) self.assertNotEqual(c, None) - c = Gdk.Cursor(cursor_type=Gdk.CursorType.WATCH) + with capture_glib_deprecation_warnings(): + c = Gdk.Cursor(cursor_type=Gdk.CursorType.WATCH) self.assertNotEqual(c, None) display_manager = Gdk.DisplayManager.get() @@ -111,31 +236,90 @@ class TestGdk(unittest.TestCase): 5, 10) - c = Gdk.Cursor(display, - test_pixbuf, - y=0, x=0) + with capture_glib_deprecation_warnings() as warn: + c = Gdk.Cursor(display, + test_pixbuf, + y=0, x=0) + self.assertNotEqual(c, None) + + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*new_from_pixbuf.*') - self.assertNotEqual(c, None) self.assertRaises(ValueError, Gdk.Cursor, 1, 2, 3) + with capture_glib_deprecation_warnings() as warn: + c = Gdk.Cursor(display, Gdk.CursorType.WATCH) + assert len(warn) == 1 + # on macOS the type is switched to PIXMAP behind the scenes + assert c.props.cursor_type in ( + Gdk.CursorType.WATCH, Gdk.CursorType.CURSOR_IS_PIXMAP) + assert c.props.display == display + + @unittest.skipUnless(GDK4, "only gdk4") + def test_cursor_gdk4(self): + Gdk.Cursor() + Gdk.Cursor(name="foo") + Gdk.Cursor(fallback=Gdk.Cursor()) + def test_flags(self): self.assertEqual(Gdk.ModifierType.META_MASK | 0, 0x10000000) self.assertEqual(hex(Gdk.ModifierType.META_MASK), '0x10000000') self.assertEqual(str(Gdk.ModifierType.META_MASK), - '<flags GDK_META_MASK of type GdkModifierType>') + '<flags GDK_META_MASK of type Gdk.ModifierType>') - self.assertEqual(Gdk.ModifierType.RELEASE_MASK | 0, 0x40000000) - self.assertEqual(hex(Gdk.ModifierType.RELEASE_MASK), '0x40000000') - self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK), - '<flags GDK_RELEASE_MASK of type GdkModifierType>') + # RELEASE_MASK does not exist in gdk4 + if not GDK4: + self.assertEqual(Gdk.ModifierType.RELEASE_MASK | 0, 0x40000000) + self.assertEqual(hex(Gdk.ModifierType.RELEASE_MASK), '0x40000000') + self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK), + '<flags GDK_RELEASE_MASK of type Gdk.ModifierType>') - self.assertEqual(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK, 0x50000000) - self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK), - '<flags GDK_META_MASK | GDK_RELEASE_MASK of type GdkModifierType>') + self.assertEqual(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK, 0x50000000) + self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK), + '<flags GDK_META_MASK | GDK_RELEASE_MASK of type Gdk.ModifierType>') + @unittest.skipIf(GDK4, "not in gdk4") def test_color_parse(self): - c = Gdk.color_parse('#00FF80') + with capture_glib_deprecation_warnings(): + c = Gdk.color_parse('#00FF80') self.assertEqual(c.red, 0) self.assertEqual(c.green, 65535) self.assertEqual(c.blue, 32896) self.assertEqual(Gdk.color_parse('bogus'), None) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_color_representations(self): + # __repr__ should generate a string which is parsable when possible + # http://docs.python.org/2/reference/datamodel.html#object.__repr__ + color = Gdk.Color(red=65535, green=32896, blue=1) + self.assertEqual(eval(repr(color)), color) + + rgba = Gdk.RGBA(red=1.0, green=0.8, blue=0.6, alpha=0.4) + self.assertEqual(eval(repr(rgba)), rgba) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_rectangle_functions(self): + # https://bugzilla.gnome.org/show_bug.cgi?id=756364 + a = Gdk.Rectangle() + b = Gdk.Rectangle() + self.assertTrue(isinstance(Gdk.rectangle_union(a, b), Gdk.Rectangle)) + intersect, rect = Gdk.rectangle_intersect(a, b) + self.assertTrue(isinstance(rect, Gdk.Rectangle)) + self.assertTrue(isinstance(intersect, bool)) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_atom_repr_str(self): + atom = Gdk.atom_intern("", True) + assert re.match(r"<Gdk.Atom\(\d+\)>", repr(atom)) + assert re.match(r"Gdk.Atom<\d+>", str(atom)) + + @unittest.skipUnless(GDK4, "only in gdk4") + @unittest.skipUnless(gtkver() >= (4, 8, 0), "constructor available since 4.8") + def test_file_list(self): + f = Gio.File.new_for_path("/tmp") + filelist = Gdk.FileList([f]) + self.assertTrue(isinstance(filelist, Gdk.FileList)) + self.assertEqual(len(filelist), 1) + self.assertEqual(filelist[0], f) diff --git a/tests/test_overrides_gdkpixbuf.py b/tests/test_overrides_gdkpixbuf.py new file mode 100644 index 0000000..4edd9bf --- /dev/null +++ b/tests/test_overrides_gdkpixbuf.py @@ -0,0 +1,47 @@ +# Copyright 2018 Christoph Reiter <reiter.christoph@gmail.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + +import pytest + +from gi import PyGIDeprecationWarning +GdkPixbuf = pytest.importorskip("gi.repository.GdkPixbuf") + + +def test_new_from_data(): + width = 600 + height = 32769 + pixbuf = GdkPixbuf.Pixbuf.new( + GdkPixbuf.Colorspace.RGB, True, 8, width, height) + pixels = pixbuf.get_pixels() + new_pixbuf = GdkPixbuf.Pixbuf.new_from_data( + pixels, GdkPixbuf.Colorspace.RGB, True, 8, + pixbuf.get_width(), pixbuf.get_height(), pixbuf.get_rowstride()) + del pixbuf + del pixels + new_pixels = new_pixbuf.get_pixels() + assert len(new_pixels) == width * height * 4 + + +def test_new_from_data_deprecated_args(): + GdkPixbuf.Pixbuf.new_from_data(b"1234", 0, True, 8, 1, 1, 4) + GdkPixbuf.Pixbuf.new_from_data(b"1234", 0, True, 8, 1, 1, 4, None) + with pytest.warns(PyGIDeprecationWarning, match=".*destroy_fn.*"): + GdkPixbuf.Pixbuf.new_from_data( + b"1234", 0, True, 8, 1, 1, 4, object(), object(), object()) + with pytest.warns(PyGIDeprecationWarning, match=".*destroy_fn_data.*"): + GdkPixbuf.Pixbuf.new_from_data( + b"1234", 0, True, 8, 1, 1, 4, object(), object(), object()) diff --git a/tests/test_overrides_gio.py b/tests/test_overrides_gio.py new file mode 100644 index 0000000..db09859 --- /dev/null +++ b/tests/test_overrides_gio.py @@ -0,0 +1,360 @@ +import random +import platform +import warnings + +import pytest + +from gi.repository import Gio, GObject +from gi import PyGIWarning + + +class Item(GObject.Object): + _id = 0 + + def __init__(self, **kwargs): + super(Item, self).__init__(**kwargs) + Item._id += 1 + self._id = self._id + + def __repr__(self): + return str(self._id) + + +class NamedItem(Item): + + name = GObject.Property(type=str, default='') + + def __repr__(self): + return self.props.name + + +def test_list_store_sort(): + store = Gio.ListStore() + items = [NamedItem(name=n) for n in "cabx"] + sorted_items = sorted(items, key=lambda i: i.props.name) + + user_data = [object(), object()] + + def sort_func(a, b, *args): + assert list(args) == user_data + assert isinstance(a, NamedItem) + assert isinstance(b, NamedItem) + cmp = lambda a, b: (a > b) - (a < b) + return cmp(a.props.name, b.props.name) + + store[:] = items + assert store[:] != sorted_items + store.sort(sort_func, *user_data) + assert store[:] == sorted_items + + +def test_list_store_insert_sorted(): + store = Gio.ListStore() + items = [NamedItem(name=n) for n in "cabx"] + sorted_items = sorted(items, key=lambda i: i.props.name) + + user_data = [object(), object()] + + def sort_func(a, b, *args): + assert list(args) == user_data + assert isinstance(a, NamedItem) + assert isinstance(b, NamedItem) + cmp = lambda a, b: (a > b) - (a < b) + return cmp(a.props.name, b.props.name) + + for item in items: + index = store.insert_sorted(item, sort_func, *user_data) + assert isinstance(index, int) + assert store[:] == sorted_items + + +def test_list_model_len(): + model = Gio.ListStore.new(Item) + assert len(model) == 0 + assert not model + for i in range(1, 10): + model.append(Item()) + assert len(model) == i + assert model + model.remove_all() + assert not model + assert len(model) == 0 + + +def test_list_model_get_item_simple(): + model = Gio.ListStore.new(Item) + with pytest.raises(IndexError): + model[0] + first_item = Item() + model.append(first_item) + assert model[0] is first_item + assert model[-1] is first_item + second_item = Item() + model.append(second_item) + assert model[1] is second_item + assert model[-1] is second_item + assert model[-2] is first_item + with pytest.raises(IndexError): + model[-3] + + with pytest.raises(TypeError): + model[object()] + + +def test_list_model_get_item_slice(): + model = Gio.ListStore.new(Item) + source = [Item() for i in range(30)] + for i in source: + model.append(i) + assert model[1:10] == source[1:10] + assert model[1:-2] == source[1:-2] + assert model[-4:-1] == source[-4:-1] + assert model[-100:-1] == source[-100:-1] + assert model[::-1] == source[::-1] + assert model[:] == source[:] + + +def test_list_model_contains(): + model = Gio.ListStore.new(Item) + item = Item() + model.append(item) + assert item in model + assert Item() not in model + with pytest.raises(TypeError): + object() in model + with pytest.raises(TypeError): + None in model + + +def test_list_model_iter(): + model = Gio.ListStore.new(Item) + item = Item() + model.append(item) + + it = iter(model) + assert next(it) is item + repr(item) + + +def test_list_store_delitem_simple(): + store = Gio.ListStore.new(Item) + store.append(Item()) + del store[0] + assert not store + with pytest.raises(IndexError): + del store[0] + with pytest.raises(IndexError): + del store[-1] + + store.append(Item()) + with pytest.raises(IndexError): + del store[-2] + del store[-1] + assert not store + + source = [Item(), Item()] + store.append(source[0]) + store.append(source[1]) + del store[-1] + assert store[:] == [source[0]] + + with pytest.raises(TypeError): + del store[object()] + + +def test_list_store_delitem_slice(): + + def do_del(count, key): + + events = [] + + def on_changed(m, *args): + events.append(args) + + store = Gio.ListStore.new(Item) + source = [Item() for i in range(count)] + for item in source: + store.append(item) + store.connect("items-changed", on_changed) + source.__delitem__(key) + store.__delitem__(key) + assert source == store[:] + return events + + values = [None, 1, -15, 3, -2, 0, -3, 5, 7] + variants = set() + for i in range(500): + start = random.choice(values) + stop = random.choice(values) + step = random.choice(values) + length = abs(random.choice(values) or 0) + if step == 0: + step += 1 + variants.add((length, start, stop, step)) + + for length, start, stop, step in variants: + do_del(length, slice(start, stop, step)) + + # basics + do_del(10, slice(None, None, None)) + do_del(10, slice(None, None, None)) + do_del(10, slice(None, None, -1)) + do_del(10, slice(0, 5, None)) + do_del(10, slice(0, 10, 1)) + do_del(10, slice(0, 10, 2)) + do_del(10, slice(14, 2, -1)) + + # test some fast paths + assert do_del(100, slice(None, None, None)) == [(0, 100, 0)] + assert do_del(100, slice(None, None, -1)) == [(0, 100, 0)] + assert do_del(100, slice(0, 50, 1)) == [(0, 50, 0)] + + +def test_list_store_setitem_simple(): + + store = Gio.ListStore.new(Item) + first = Item() + store.append(first) + + class Wrong(GObject.Object): + pass + + with pytest.raises(TypeError): + store[0] = object() + with pytest.raises(TypeError): + store[0] = None + with pytest.raises(TypeError): + store[0] = Wrong() + + assert store[:] == [first] + + new = Item() + store[0] = new + assert len(store) == 1 + store[-1] = Item() + assert len(store) == 1 + + with pytest.raises(IndexError): + store[1] = Item() + with pytest.raises(IndexError): + store[-2] = Item() + + store = Gio.ListStore.new(Item) + source = [Item(), Item(), Item()] + for item in source: + store.append(item) + new = Item() + store[1] = new + assert store[:] == [source[0], new, source[2]] + + with pytest.raises(TypeError): + store[object()] = Item() + + +def test_list_store_setitem_slice(): + + def do_set(count, key, new_count): + if count == 0 and key.step is not None \ + and platform.python_implementation() == "PyPy": + # https://foss.heptapod.net/pypy/pypy/-/issues/2804 + return + store = Gio.ListStore.new(Item) + source = [Item() for i in range(count)] + new = [Item() for i in range(new_count)] + for item in source: + store.append(item) + source_error = None + try: + source.__setitem__(key, new) + except ValueError as e: + source_error = type(e) + + store_error = None + try: + store.__setitem__(key, new) + except Exception as e: + store_error = type(e) + + assert source_error == store_error + assert source == store[:] + + values = [None, 1, -15, 3, -2, 0, 3, 4, 100] + variants = set() + for i in range(500): + start = random.choice(values) + stop = random.choice(values) + step = random.choice(values) + length = abs(random.choice(values) or 0) + new = random.choice(values) or 0 + if step == 0: + step += 1 + variants.add((length, start, stop, step, new)) + + for length, start, stop, step, new in variants: + do_set(length, slice(start, stop, step), new) + + # basics + do_set(10, slice(None, None, None), 20) + do_set(10, slice(None, None, None), 0) + do_set(10, slice(None, None, -1), 20) + do_set(10, slice(None, None, -1), 10) + do_set(10, slice(0, 5, None), 20) + do_set(10, slice(0, 10, 1), 0) + + # test iterators + store = Gio.ListStore.new(Item) + store[:] = iter([Item() for i in range(10)]) + assert len(store) == 10 + + # make sure we do all or nothing + store = Gio.ListStore.new(Item) + with pytest.raises(TypeError): + store[:] = [Item(), object()] + assert len(store) == 0 + + +def test_action_map_add_action_entries(): + actionmap = Gio.SimpleActionGroup() + + test_data = [] + + def f(action, parameter, data): + test_data.append('test back') + + actionmap.add_action_entries(( + ("simple", f), + ("with_type", f, "i"), + ("with_state", f, "s", "'left'", f), + )) + assert actionmap.has_action("simple") + assert actionmap.has_action("with_type") + assert actionmap.has_action("with_state") + actionmap.add_action_entries(( + ("with_user_data", f), + ), "user_data") + assert actionmap.has_action("with_user_data") + + with pytest.raises(TypeError): + actionmap.add_action_entries(( + ("invaild_type_string", f, 'asdf'), + )) + with pytest.raises(ValueError): + actionmap.add_action_entries(( + ("stateless_with_change_state", f, None, None, f), + )) + + actionmap.activate_action("simple") + assert test_data[0] == 'test back' + + +def test_types_init_warn(): + types = [ + Gio.DBusAnnotationInfo, Gio.DBusArgInfo, Gio.DBusMethodInfo, + Gio.DBusSignalInfo, Gio.DBusInterfaceInfo, Gio.DBusNodeInfo, + ] + + for t in types: + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + t() + assert issubclass(warn[0].category, PyGIWarning) diff --git a/tests/test_overrides_glib.py b/tests/test_overrides_glib.py index 4d7e63a..08c41ae 100644 --- a/tests/test_overrides_glib.py +++ b/tests/test_overrides_glib.py @@ -1,11 +1,121 @@ # -*- Mode: Python; py-indent-offset: 4 -*- # vim: tabstop=4 shiftwidth=4 expandtab +import os +import gc import unittest +import tempfile +import socket + +import pytest import gi from gi.repository import GLib -from compathelper import _long + +from .helper import capture_gi_deprecation_warnings + + +def test_io_add_watch_get_args(): + get_args = GLib._io_add_watch_get_args + func = lambda: None + + # create a closed channel for testing + fd, fn = tempfile.mkstemp() + os.close(fd) + try: + chan = GLib.IOChannel(filename=fn) + chan.shutdown(True) + finally: + os.remove(fn) + + # old way + with capture_gi_deprecation_warnings(): + assert get_args(chan, GLib.IOCondition.IN, func) == ( + chan, 0, GLib.IOCondition.IN, func, tuple()) + + with pytest.raises(TypeError): + get_args(chan, GLib.IOCondition.IN, object()) + + # new way + prio = GLib.PRIORITY_DEFAULT + with capture_gi_deprecation_warnings(): + assert get_args(chan, prio, GLib.IOCondition.IN, func, 99) == \ + (chan, prio, GLib.IOCondition.IN, func, (99,)) + + with pytest.raises(TypeError): + assert get_args(chan, prio, GLib.IOCondition.IN) + + with pytest.raises(TypeError): + assert get_args(chan, prio, 99) + + +@pytest.mark.skipif(os.name != "nt", reason="windows only") +def test_io_add_watch_get_args_win32_socket(): + get_args = GLib._io_add_watch_get_args + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + func = lambda: None + prio = GLib.PRIORITY_DEFAULT + chan = get_args(s, prio, GLib.IOCondition.IN, func)[0] + assert isinstance(chan, GLib.IOChannel) + chan.shutdown(False) + + +def test_threads_init(): + with capture_gi_deprecation_warnings() as w: + GLib.threads_init() + assert len(w) + + +def test_gerror_matches(): + e = GLib.Error(domain=42, code=24) + assert e.matches(42, 24) + + +def test_timeout_add_seconds(): + h = GLib.timeout_add_seconds( + 100, lambda *x: None, 1, 2, 3, priority=GLib.PRIORITY_HIGH_IDLE) + GLib.source_remove(h) + + +def test_iochannel(): + with pytest.raises(TypeError): + GLib.IOChannel() + + +def test_iochannel_write(): + fd, fn = tempfile.mkstemp() + os.close(fd) + chan = GLib.IOChannel(filename=fn, mode="r+") + try: + assert chan.write(b"foo", 2) == 2 + chan.seek(0) + assert chan.read() == b"fo" + finally: + chan.shutdown(True) + + +@pytest.mark.skipif(os.name == "nt", reason="unix only") +def test_has_unix_signal_add(): + with capture_gi_deprecation_warnings(): + GLib.unix_signal_add == GLib.unix_signal_add_full + + +@pytest.mark.skipif(os.name != "nt", reason="windows only") +def test_iochannel_win32(): + fd, fn = tempfile.mkstemp() + closed = False + try: + channel = GLib.IOChannel(hwnd=fd) + try: + assert channel.read() == b"" + finally: + closed = True + channel.shutdown(True) + finally: + if not closed: + os.close(fd) + os.remove(fn) class TestGVariant(unittest.TestCase): @@ -22,6 +132,17 @@ class TestGVariant(unittest.TestCase): self.assertTrue(isinstance(variant, GLib.Variant)) self.assertEqual(variant.get_string(), 'hello') + def test_simple_invalid_ops(self): + variant = GLib.Variant('i', 42) + with pytest.raises(TypeError): + len(variant) + + with pytest.raises(TypeError): + variant[0] + + with pytest.raises(TypeError): + variant.keys() + def test_create_variant(self): variant = GLib.Variant('v', GLib.Variant('i', 42)) self.assertTrue(isinstance(variant, GLib.Variant)) @@ -59,7 +180,13 @@ class TestGVariant(unittest.TestCase): # nested tuples variant = GLib.Variant('((si)(ub))', (('hello', -1), (42, True))) self.assertEqual(variant.get_type_string(), '((si)(ub))') - self.assertEqual(variant.unpack(), (('hello', -1), (_long(42), True))) + self.assertEqual(variant.unpack(), (('hello', -1), (42, True))) + + def test_new_tuple_sink(self): + # https://bugzilla.gnome.org/show_bug.cgi?id=735166 + variant = GLib.Variant.new_tuple(GLib.Variant.new_tuple()) + del variant + gc.collect() def test_create_dictionary(self): variant = GLib.Variant('a{si}', {}) @@ -88,6 +215,15 @@ class TestGVariant(unittest.TestCase): self.assertTrue(isinstance(variant, GLib.Variant)) self.assertEqual(variant.unpack(), d) + # init with an iterable + variant = GLib.Variant('a{si}', [("foo", 2)]) + assert variant.unpack() == {'foo': 2} + + with pytest.raises(TypeError): + GLib.Variant('a{si}', [("foo",)]) + with pytest.raises(TypeError): + GLib.Variant('a{si}', [("foo", 1, 2)]) + def test_create_array(self): variant = GLib.Variant('ai', []) self.assertEqual(variant.get_type_string(), 'ai') @@ -133,6 +269,50 @@ class TestGVariant(unittest.TestCase): self.assertEqual(variant.get_type_string(), 'aai') self.assertEqual(variant.unpack(), [[1, 2], [3, 4, 5]]) + def test_create_array_guchar(self): + variant = GLib.Variant('ay', [97, 97, 97]) + assert variant.unpack() == [97, 97, 97] + + variant = GLib.Variant('ay', b'aaa') + assert variant.unpack() == [97, 97, 97] + + variant = GLib.Variant('ay', iter([1, 2, 3])) + assert variant.unpack() == [1, 2, 3] + + with self.assertRaises(TypeError): + GLib.Variant('ay', u'aaa') + + with self.assertRaises(TypeError): + GLib.Variant('ay', object()) + + def test_create_maybe(self): + variant = GLib.Variant('mai', None) + self.assertEqual(variant.get_type_string(), 'mai') + self.assertEqual(variant.n_children(), 0) + self.assertEqual(variant.unpack(), None) + + variant = GLib.Variant('mai', []) + self.assertEqual(variant.get_type_string(), 'mai') + self.assertEqual(variant.n_children(), 1) + + variant = GLib.Variant('mami', [None]) + self.assertEqual(variant.get_type_string(), 'mami') + self.assertEqual(variant.n_children(), 1) + + variant = GLib.Variant('mami', [None, 13, None]) + self.assertEqual(variant.get_type_string(), 'mami') + self.assertEqual(variant.n_children(), 1) + array = variant.get_child_value(0) + self.assertEqual(array.n_children(), 3) + + element = array.get_child_value(0) + self.assertEqual(element.n_children(), 0) + element = array.get_child_value(1) + self.assertEqual(element.n_children(), 1) + self.assertEqual(element.get_child_value(0).get_int32(), 13) + element = array.get_child_value(2) + self.assertEqual(element.n_children(), 0) + def test_create_complex(self): variant = GLib.Variant('(as)', ([],)) self.assertEqual(variant.get_type_string(), '(as)') @@ -225,8 +405,8 @@ class TestGVariant(unittest.TestCase): self.assertRaises(TypeError, GLib.Variant, '(ss)', 'mec', 'mac') self.assertRaises(TypeError, GLib.Variant, '(s)', 'hello') - # unimplemented data type - self.assertRaises(NotImplementedError, GLib.Variant, 'Q', 1) + # invalid format string + self.assertRaises(TypeError, GLib.Variant, 'Q', 1) # invalid types self.assertRaises(TypeError, GLib.Variant, '(ii', (42, 3)) @@ -273,12 +453,16 @@ class TestGVariant(unittest.TestCase): self.assertEqual(res, {'key1': 1, 'key2': 2}) # maybe - v = GLib.Variant.new_maybe(GLib.VariantType.new('i'), GLib.Variant('i', 1)) - res = v.unpack() - self.assertEqual(res, 1) - v = GLib.Variant.new_maybe(GLib.VariantType.new('i'), None) - res = v.unpack() - self.assertEqual(res, None) + v = GLib.Variant('mi', 1) + self.assertEqual(v.unpack(), 1) + v = GLib.Variant('mi', None) + self.assertEqual(v.unpack(), None) + v = GLib.Variant('mai', []) + self.assertEqual(v.unpack(), []) + v = GLib.Variant('m()', ()) + self.assertEqual(v.unpack(), ()) + v = GLib.Variant('mami', [None, 1, None]) + self.assertEqual(v.unpack(), [None, 1, None]) def test_iteration(self): # array index access @@ -414,6 +598,9 @@ class TestGVariant(unittest.TestCase): assert_equal('v', GLib.Variant('i', 42)) assert_not_equal('v', GLib.Variant('i', 42), 'v', GLib.Variant('i', 43)) + assert GLib.Variant('i', 42) != object() + assert not GLib.Variant('i', 42) == object() + def test_bool(self): # Check if the GVariant bool matches the unpacked Pythonic bool @@ -470,6 +657,11 @@ class TestGVariant(unittest.TestCase): assert_equals_bool('v', GLib.Variant('i', 0)) assert_equals_bool('v', GLib.Variant('i', 1)) + # maybe types + assert_equals_bool('mi', 42) + assert_equals_bool('mi', 0) + assert_equals_bool('mi', None) + def test_repr(self): # with C constructor v = GLib.Variant.new_uint32(42) @@ -487,3 +679,46 @@ class TestGVariant(unittest.TestCase): # with override constructor v = GLib.Variant('(is)', (1, 'somestring')) self.assertEqual(str(v), "(1, 'somestring')") + + def test_parse_error(self): + # This test doubles as a test for GLib.Error marshaling. + source_str = 'abc' + with self.assertRaises(GLib.Error) as context: + GLib.Variant.parse(None, source_str, None, None) + e = context.exception + text = GLib.Variant.parse_error_print_context(e, source_str) + self.assertTrue(source_str in text) + + def test_parse_error_exceptions(self): + source_str = 'abc' + self.assertRaisesRegex(TypeError, 'Must be GLib.Error, not int', + GLib.Variant.parse_error_print_context, + 42, source_str) + + gerror = GLib.Error(message=42) # not a string + self.assertRaisesRegex(TypeError, ".*Must be string, not int.*", + GLib.Variant.parse_error_print_context, + gerror, source_str) + + gerror = GLib.Error(domain=42) # not a string + self.assertRaisesRegex(TypeError, ".*Must be string, not int.*", + GLib.Variant.parse_error_print_context, + gerror, source_str) + + gerror = GLib.Error(code='not an int') + self.assertRaisesRegex(TypeError, ".*Must be number, not str.*", + GLib.Variant.parse_error_print_context, + gerror, source_str) + + gerror = GLib.Error(code=GLib.MAXUINT) + self.assertRaisesRegex(OverflowError, + ".*not in range.*", + GLib.Variant.parse_error_print_context, + gerror, source_str) + + +class TestConstants(unittest.TestCase): + + def test_basic_types_limits(self): + self.assertTrue(isinstance(GLib.MINFLOAT, float)) + self.assertTrue(isinstance(GLib.MAXLONG, int)) diff --git a/tests/test_overrides_gobject.py b/tests/test_overrides_gobject.py new file mode 100644 index 0000000..dc26596 --- /dev/null +++ b/tests/test_overrides_gobject.py @@ -0,0 +1,423 @@ +import pytest + +from gi import PyGIDeprecationWarning +from gi.repository import GObject, GLib, GIMarshallingTests + +from .helper import ignore_gi_deprecation_warnings + + +def test_stop_emission_deprec(): + class TestObject(GObject.GObject): + int_prop = GObject.Property(default=0, type=int) + + obj = TestObject() + + def notify_callback(obj, *args): + with pytest.warns(PyGIDeprecationWarning): + obj.stop_emission("notify::int-prop") + + with pytest.warns(PyGIDeprecationWarning): + obj.emit_stop_by_name("notify::int-prop") + + obj.stop_emission_by_name("notify::int-prop") + + obj.connect("notify::int-prop", notify_callback) + obj.notify("int-prop") + + +def test_signal_parse_name(): + obj = GObject.GObject() + assert GObject.signal_parse_name("notify", obj, True) == (1, 0) + + with pytest.raises(ValueError): + GObject.signal_parse_name("foobar", obj, True) + + +def test_signal_query(): + obj = GObject.GObject() + res = GObject.signal_query("notify", obj) + assert res.signal_name == "notify" + assert res.itype == obj.__gtype__ + + res = GObject.signal_query("foobar", obj) + assert res is None + + +def test_value_repr(): + v = GObject.Value() + assert repr(v) == "<Value (invalid) None>" + + v = GObject.Value(int, 0) + assert repr(v) == "<Value (gint) 0>" + + +def test_value_no_init(): + v = GObject.Value() + with pytest.raises(TypeError): + v.set_value(0) + v.init(GObject.TYPE_LONG) + assert v.get_value() == 0 + v.set_value(0) + + +def test_value_invalid_type(): + v = GObject.Value() + assert v.g_type == GObject.TYPE_INVALID + assert isinstance(GObject.TYPE_INVALID, GObject.GType) + with pytest.raises(ValueError, match="Invalid GType"): + v.init(GObject.TYPE_INVALID) + + with pytest.raises( + TypeError, match="GObject.Value needs to be initialized first"): + v.set_value(None) + + assert v.get_value() is None + + +def test_value_long(): + v = GObject.Value(GObject.TYPE_LONG) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXLONG) + assert v.get_value() == GLib.MAXLONG + + v.set_value(GLib.MINLONG) + assert v.get_value() == GLib.MINLONG + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXLONG + 1) + + with pytest.raises(OverflowError): + v.set_value(GLib.MINLONG - 1) + + +def test_value_ulong(): + v = GObject.Value(GObject.TYPE_ULONG) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXULONG) + assert v.get_value() == GLib.MAXULONG + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXULONG + 1) + + with pytest.raises(OverflowError): + v.set_value(-1) + + with pytest.raises(TypeError): + v.set_value(object()) + + with pytest.raises(TypeError): + v.set_value(None) + + +def test_value_float(): + v = GObject.Value(GObject.TYPE_FLOAT) + + for getter, setter in [(v.get_value, v.set_value), + (v.get_float, v.set_float)]: + + assert getter() == 0.0 + setter(0) + assert getter() == 0 + + setter(GLib.MAXFLOAT) + assert getter() == GLib.MAXFLOAT + + setter(GLib.MINFLOAT) + assert getter() == GLib.MINFLOAT + + setter(-GLib.MAXFLOAT) + assert getter() == -GLib.MAXFLOAT + + with pytest.raises(OverflowError): + setter(GLib.MAXFLOAT * 2) + + with pytest.raises(OverflowError): + setter(-GLib.MAXFLOAT * 2) + + with pytest.raises(TypeError): + setter(object()) + + with pytest.raises(TypeError): + setter(None) + + with pytest.raises(TypeError): + setter(1j) + + v.reset() + + +def test_value_double(): + v = GObject.Value(GObject.TYPE_DOUBLE) + assert v.get_value() == 0.0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXDOUBLE) + assert v.get_value() == GLib.MAXDOUBLE + + v.set_value(GLib.MINDOUBLE) + assert v.get_value() == GLib.MINDOUBLE + + v.set_value(-GLib.MAXDOUBLE) + assert v.get_value() == -GLib.MAXDOUBLE + + with pytest.raises(TypeError): + v.set_value(object()) + + with pytest.raises(TypeError): + v.set_value(None) + + with pytest.raises(TypeError): + v.set_value(1j) + + +def test_value_uint64(): + v = GObject.Value(GObject.TYPE_UINT64) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXUINT64) + assert v.get_value() == GLib.MAXUINT64 + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXUINT64 + 1) + + with pytest.raises(OverflowError): + v.set_value(-1) + + +def test_value_int64(): + v = GObject.Value(GObject.TYPE_INT64) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXINT64) + assert v.get_value() == GLib.MAXINT64 + v.set_value(GLib.MININT64) + assert v.get_value() == GLib.MININT64 + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXINT64 + 1) + + with pytest.raises(OverflowError): + v.set_value(GLib.MININT64 - 1) + + with pytest.raises(TypeError): + v.set_value(object()) + + with pytest.raises(TypeError): + v.set_value(None) + + +def test_value_pointer(): + v = GObject.Value(GObject.TYPE_POINTER) + assert v.get_value() == 0 + v.set_value(42) + assert v.get_value() == 42 + v.set_value(0) + assert v.get_value() == 0 + + +def test_value_unichar(): + assert GObject.TYPE_UNICHAR == GObject.TYPE_UINT + + v = GObject.Value(GObject.TYPE_UNICHAR) + assert v.get_value() == 0 + v.set_value(42) + assert v.get_value() == 42 + + v.set_value(GLib.MAXUINT) + assert v.get_value() == GLib.MAXUINT + + +def test_value_gtype(): + class TestObject(GObject.GObject): + pass + + v = GObject.Value(GObject.TYPE_GTYPE) + assert v.get_value() == GObject.TYPE_INVALID + v.set_value(TestObject.__gtype__) + assert v.get_value() == TestObject.__gtype__ + v.set_value(TestObject) + assert v.get_value() == TestObject.__gtype__ + + with pytest.raises(TypeError): + v.set_value(None) + + +def test_value_variant(): + v = GObject.Value(GObject.TYPE_VARIANT) + assert v.get_value() is None + variant = GLib.Variant('i', 42) + v.set_value(variant) + assert v.get_value() == variant + + v.set_value(None) + assert v.get_value() is None + + with pytest.raises(TypeError): + v.set_value(object()) + + +def test_value_param(): + # FIXME: set_value and get_value trigger a critical + # GObject.Value(GObject.TYPE_PARAM) + pass + + +def test_value_string(): + v = GObject.Value(GObject.TYPE_STRING) + for getter, setter in [(v.get_value, v.set_value), + (v.get_string, v.set_string)]: + + assert getter() is None + + with pytest.raises(TypeError): + setter(b"bar") + + setter(u"quux") + assert getter() == u"quux" + assert isinstance(getter(), str) + + setter(None) + assert getter() is None + + v.reset() + + +def test_value_pyobject(): + class Foo(object): + pass + + v = GObject.Value(GObject.TYPE_PYOBJECT) + assert v.get_value() is None + for obj in [Foo(), None, 42, "foo"]: + v.set_value(obj) + assert v.get_value() == obj + + +@ignore_gi_deprecation_warnings +def test_value_char(): + v = GObject.Value(GObject.TYPE_CHAR) + assert v.get_value() == 0 + v.set_value(42) + assert v.get_value() == 42 + v.set_value(-1) + assert v.get_value() == -1 + v.set_value(b"a") + assert v.get_value() == 97 + v.set_value(b"\x00") + assert v.get_value() == 0 + + with pytest.raises(TypeError): + v.set_value(u"a") + + with pytest.raises(OverflowError): + v.set_value(128) + + +def test_value_uchar(): + v = GObject.Value(GObject.TYPE_UCHAR) + assert v.get_value() == 0 + v.set_value(200) + assert v.get_value() == 200 + v.set_value(b"a") + assert v.get_value() == 97 + v.set_value(b"\x00") + assert v.get_value() == 0 + + with pytest.raises(TypeError): + v.set_value(u"a") + + with pytest.raises(OverflowError): + v.set_value(256) + + +def test_value_set_boxed_deprecate_non_boxed(): + v = GObject.Value(GObject.TYPE_POINTER) + with pytest.warns(PyGIDeprecationWarning): + v.get_boxed() + with pytest.warns(PyGIDeprecationWarning): + v.set_boxed(None) + + +def test_value_boolean(): + v = GObject.Value(GObject.TYPE_BOOLEAN) + for getter, setter in [(v.get_value, v.set_value), + (v.get_boolean, v.set_boolean)]: + assert getter() is False + assert isinstance(getter(), bool) + + setter(42) + assert getter() is True + setter(-1) + assert getter() is True + setter(0) + assert getter() is False + + setter([]) + assert getter() is False + setter(["foo"]) + assert getter() is True + + setter(None) + assert getter() is False + v.reset() + + +def test_value_enum(): + t = GIMarshallingTests.GEnum + v = GObject.Value(t) + + for getter, setter in [(v.get_value, v.set_value), + (v.get_enum, v.set_enum)]: + assert v.g_type == t.__gtype__ + assert getter() == 0 + + setter(t.VALUE1) + assert getter() == t.VALUE1 + # FIXME: we should try to return an enum type + assert type(getter()) is int + + setter(2424242) + assert getter() == 2424242 + + setter(-1) + assert getter() == -1 + + with pytest.raises(TypeError): + setter(object()) + + with pytest.raises(TypeError): + setter(None) + + v.reset() + + +def test_value_object(): + v = GObject.Value(GIMarshallingTests.Object) + assert v.g_type.is_a(GObject.TYPE_OBJECT) + + for getter, setter in [(v.get_value, v.set_value), + (v.get_object, v.set_object)]: + assert getter() is None + + setter(None) + assert getter() is None + + obj = GIMarshallingTests.Object() + setter(obj) + assert getter() is obj + + with pytest.raises(TypeError): + setter(object()) + + v.reset() diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py index fbe51ec..04ab718 100644 --- a/tests/test_overrides_gtk.py +++ b/tests/test_overrides_gtk.py @@ -1,23 +1,42 @@ # -*- Mode: Python; py-indent-offset: 4 -*- -# coding: UTF-8 # vim: tabstop=4 shiftwidth=4 expandtab import contextlib import unittest -import time import sys +import gc +import warnings +import timeit -from compathelper import _unicode, _bytes +import pytest + +from .helper import ignore_gi_deprecation_warnings, capture_glib_warnings import gi.overrides import gi.types -from gi.repository import GLib, GObject +from gi.repository import Gio, GLib, GObject try: - from gi.repository import GdkPixbuf, Gdk, Gtk - Gtk # pyflakes + from gi.repository import Gtk, GdkPixbuf, Gdk + PyGTKDeprecationWarning = Gtk.PyGTKDeprecationWarning + Gtk_version = Gtk._version except ImportError: Gtk = None + Gtk_version = None + PyGTKDeprecationWarning = None + GdkPixbuf = None + Gdk = None + + +def gtkver(): + if Gtk is None: + return (0, 0, 0) + return (Gtk.get_major_version(), + Gtk.get_minor_version(), + Gtk.get_micro_version()) + + +GTK4 = (Gtk._version == "4.0") @contextlib.contextmanager @@ -32,28 +51,132 @@ def realized(widget): if isinstance(widget, Gtk.Window): toplevel = widget else: - toplevel = widget.get_parent_window() + if Gtk._version == "4.0": + toplevel = widget.get_parent() + else: + toplevel = widget.get_parent_window() if toplevel is None: window = Gtk.Window() - window.add(widget) + if Gtk._version == "4.0": + window.set_child(widget) + else: + window.add(widget) + window.show() widget.realize() - while Gtk.events_pending(): - Gtk.main_iteration() + if Gtk._version == "4.0": + context = GLib.MainContext() + while context.pending(): + context.iteration(False) + else: + while Gtk.events_pending(): + Gtk.main_iteration() + assert widget.get_realized() yield widget if toplevel is None: - window.remove(widget) + if Gtk._version == "4.0": + window.set_child(None) + else: + window.remove(widget) + window.destroy() - while Gtk.events_pending(): - Gtk.main_iteration() + if Gtk._version == "4.0": + context = GLib.MainContext() + while context.pending(): + context.iteration(False) + else: + while Gtk.events_pending(): + Gtk.main_iteration() @unittest.skipUnless(Gtk, 'Gtk not available') +@unittest.skipIf(Gtk_version == "4.0", "not in gtk4") +def test_freeze_child_notif(): + + events = [] + + def on_notify(widget, spec): + events.append(spec.name) + + b = Gtk.Box() + c = Gtk.Button() + c.connect("child-notify", on_notify) + c.freeze_child_notify() + b.pack_start(c, True, True, 0) + b.child_set_property(c, "pack-type", Gtk.PackType.END) + b.child_set_property(c, "pack-type", Gtk.PackType.START) + c.thaw_child_notify() + assert events.count("pack-type") == 1 + del events[:] + + with c.freeze_child_notify(): + b.child_set_property(c, "pack-type", Gtk.PackType.END) + b.child_set_property(c, "pack-type", Gtk.PackType.START) + + assert events.count("pack-type") == 1 + + +@unittest.skipUnless(Gtk, 'Gtk not available') +@unittest.skipIf(Gtk_version == "4.0", "not in gtk4") +def test_menu_popup(): + m = Gtk.Menu() + with capture_glib_warnings(): + m.popup(None, None, None, None, 0, 0) + m.popdown() + + +@unittest.skipUnless(Gtk, 'Gtk not available') +@unittest.skipIf(Gtk_version == "4.0", "not in gtk4") +def test_button_stock(): + with capture_glib_warnings(): + button = Gtk.Button(stock=Gtk.STOCK_OK) + assert button.props.label == Gtk.STOCK_OK + assert button.props.use_stock + + +@unittest.skipUnless(Gtk, 'Gtk not available') +def test_wrapper_toggle_refs(): + if not GTK4: + BASE = Gtk.Button + else: + BASE = Gtk.Widget + + class MyWidget(BASE): + def __init__(self, height): + BASE.__init__(self) + self._height = height + + def do_measure(self, orientation, for_size): + if orientation == Gtk.Orientation.VERTICAL: + return (self._height, self._height, -1, -1) + else: + return (0, 0, -1, -1) + + def do_get_preferred_height(self): + return (self._height, self._height) + + height = 142 + w = Gtk.Window() + b = MyWidget(height) + if not GTK4: + w.add(b) + b.show_all() + else: + w.set_child(b) + del b + gc.collect() + gc.collect() + assert w.get_preferred_size().minimum_size.height >= height + + +@unittest.skipUnless(Gtk, 'Gtk not available') +@ignore_gi_deprecation_warnings class TestGtk(unittest.TestCase): + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_container(self): box = Gtk.Box() self.assertTrue(isinstance(box, Gtk.Box)) @@ -68,30 +191,29 @@ class TestGtk(unittest.TestCase): self.assertTrue(label2 in box) self.assertEqual(len(box), 2) self.assertTrue(box) - l = [x for x in box] - self.assertEqual(l, [label, label2]) + labels = [x for x in box] + self.assertEqual(labels, [label, label2]) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_actions(self): self.assertEqual(Gtk.Action, gi.overrides.Gtk.Action) - self.assertRaises(TypeError, Gtk.Action) - action = Gtk.Action("test", "Test", "Test Action", Gtk.STOCK_COPY) + action = Gtk.Action(name="test", label="Test", tooltip="Test Action", stock_id=Gtk.STOCK_COPY) self.assertEqual(action.get_name(), "test") self.assertEqual(action.get_label(), "Test") self.assertEqual(action.get_tooltip(), "Test Action") self.assertEqual(action.get_stock_id(), Gtk.STOCK_COPY) self.assertEqual(Gtk.RadioAction, gi.overrides.Gtk.RadioAction) - self.assertRaises(TypeError, Gtk.RadioAction) - action = Gtk.RadioAction("test", "Test", "Test Action", Gtk.STOCK_COPY, 1) + action = Gtk.RadioAction(name="test", label="Test", tooltip="Test Action", stock_id=Gtk.STOCK_COPY, value=1) self.assertEqual(action.get_name(), "test") self.assertEqual(action.get_label(), "Test") self.assertEqual(action.get_tooltip(), "Test Action") self.assertEqual(action.get_stock_id(), Gtk.STOCK_COPY) self.assertEqual(action.get_current_value(), 1) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_actiongroup(self): self.assertEqual(Gtk.ActionGroup, gi.overrides.Gtk.ActionGroup) - self.assertRaises(TypeError, Gtk.ActionGroup) action_group = Gtk.ActionGroup(name='TestActionGroup') callback_data = "callback data" @@ -131,14 +253,61 @@ class TestGtk(unittest.TestCase): expected_results.remove(a) action.activate() + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_action_group_error_handling(self): + action_group = Gtk.ActionGroup(name='TestActionGroup') + with pytest.raises(TypeError): + action_group.add_actions(42) + + with pytest.raises(TypeError): + action_group.add_toggle_actions(42) + + with pytest.raises(TypeError): + action_group.add_radio_actions(42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_action_group_no_user_data(self): + action_group = Gtk.ActionGroup(name='TestActionGroup') + + called = [] + + def test_action_callback_no_data(action): + called.append(action) + + action_group.add_actions([ + ('test-action1', None, 'Test Action 1', + None, None, test_action_callback_no_data)]) + action_group.add_actions([('test2-action1',)]) + action_group.get_action('test-action1').activate() + + action_group.add_toggle_actions([ + ('test-action2', None, 'Test Action 2', + None, None, test_action_callback_no_data)]) + action_group.add_toggle_actions([('test2-action2',)]) + action_group.get_action('test-action2').activate() + + def test_action_callback_no_data_radio(action, current): + called.append(action) + + action_group.add_radio_actions([ + ('test-action3', None, 'Test Action 3', None, None, 0), + ('test-action4', None, 'Test Action 4', None, None, 1)], + 1, test_action_callback_no_data_radio) + action_group.add_radio_actions([('test2-action3',)]) + action = action_group.get_action('test-action3') + assert action.get_current_value() == 1 + action.activate() + + assert len(called) == 3 + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_uimanager(self): self.assertEqual(Gtk.UIManager, gi.overrides.Gtk.UIManager) ui = Gtk.UIManager() ui.add_ui_from_string("""<ui> <menubar name="menubar1"></menubar> </ui> -""" -) +""") menubar = ui.get_widget("/menubar1") self.assertEqual(type(menubar), Gtk.MenuBar) @@ -150,13 +319,18 @@ class TestGtk(unittest.TestCase): self.assertEqual(ag, groups[-2]) self.assertEqual(ag2, groups[-1]) + with pytest.raises(TypeError): + ui.add_ui_from_string(42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_uimanager_nonascii(self): ui = Gtk.UIManager() ui.add_ui_from_string(b'<ui><menubar name="menub\xc3\xa6r1" /></ui>'.decode('UTF-8')) mi = ui.get_widget("/menubær1") self.assertEqual(type(mi), Gtk.MenuBar) - def test_window(self): + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_window_gtk3(self): # standard Window w = Gtk.Window() self.assertEqual(w.get_property('type'), Gtk.WindowType.TOPLEVEL) @@ -165,10 +339,6 @@ class TestGtk(unittest.TestCase): w = Gtk.Window(type=Gtk.WindowType.POPUP) self.assertEqual(w.get_property('type'), Gtk.WindowType.POPUP) - # pygtk compatible positional argument - w = Gtk.Window(Gtk.WindowType.POPUP) - self.assertEqual(w.get_property('type'), Gtk.WindowType.POPUP) - class TestWindow(Gtk.Window): __gtype_name__ = "TestWindow" @@ -192,43 +362,146 @@ class TestGtk(unittest.TestCase): self.assertEqual(builder.get_object('testpop').get_property('type'), Gtk.WindowType.POPUP) - def test_dialogs(self): + @unittest.skipUnless(Gtk_version == "4.0", "no GtkWindowType in gtk4") + def test_window_gtk4(self): + w = Gtk.Window() + + # check that setting default size works + w.set_default_size(300, 300) + self.assertEqual(w.get_default_size(), (300, 300)) + + class TestWindow(Gtk.Window): + __gtype_name__ = "TestWindow" + + # works from builder + builder = Gtk.Builder() + builder.add_from_string(''' +<interface> + <object class="GtkWindow" id="win"> + <property name="css-name">amazing</property> + </object> + <object class="TestWindow" id="testwin"> + <property name="css-name">amazing-test</property> + </object> +</interface>''') + self.assertEqual(builder.get_object("win").get_property("css-name"), + "amazing") + self.assertEqual(builder.get_object("testwin").get_property("css-name"), + "amazing-test") + + def test_dialog_classes(self): self.assertEqual(Gtk.Dialog, gi.overrides.Gtk.Dialog) - self.assertEqual(Gtk.AboutDialog, gi.overrides.Gtk.AboutDialog) - self.assertEqual(Gtk.MessageDialog, gi.overrides.Gtk.MessageDialog) - self.assertEqual(Gtk.ColorSelectionDialog, gi.overrides.Gtk.ColorSelectionDialog) - self.assertEqual(Gtk.FileChooserDialog, gi.overrides.Gtk.FileChooserDialog) - self.assertEqual(Gtk.FontSelectionDialog, gi.overrides.Gtk.FontSelectionDialog) - self.assertEqual(Gtk.RecentChooserDialog, gi.overrides.Gtk.RecentChooserDialog) - - # Gtk.Dialog - dialog = Gtk.Dialog(title='Foo', - flags=Gtk.DialogFlags.MODAL, - buttons=('test-button1', 1)) + if not GTK4: + self.assertEqual(Gtk.FileChooserDialog, gi.overrides.Gtk.FileChooserDialog) + self.assertEqual(Gtk.RecentChooserDialog, gi.overrides.Gtk.RecentChooserDialog) + self.assertEqual(Gtk.ColorSelectionDialog, gi.overrides.Gtk.ColorSelectionDialog) + self.assertEqual(Gtk.FontSelectionDialog, gi.overrides.Gtk.FontSelectionDialog) + + def test_dialog_base(self): + dialog = Gtk.Dialog(title='Foo', modal=True) self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) - - dialog.add_buttons('test-button2', 2, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) - self.assertEqual('Foo', dialog.get_title()) self.assertTrue(dialog.get_modal()) + + @unittest.skipIf(GTK4, "flags not in gtk4") + def test_dialog_deprecations(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + dialog = Gtk.Dialog(title='Foo', flags=Gtk.DialogFlags.MODAL) + self.assertTrue(dialog.get_modal()) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*flags.*modal.*') + + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + dialog = Gtk.Dialog(title='Foo', flags=Gtk.DialogFlags.DESTROY_WITH_PARENT) + self.assertTrue(dialog.get_destroy_with_parent()) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*flags.*destroy_with_parent.*') + + @unittest.skipIf(GTK4, "flags not in gtk4") + def test_dialog_deprecation_stacklevels(self): + # Test warning levels are setup to give the correct filename for + # deprecations in different classes in the inheritance hierarchy. + + # Base class + self.assertEqual(Gtk.Dialog, gi.overrides.Gtk.Dialog) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + Gtk.Dialog(flags=Gtk.DialogFlags.MODAL) + self.assertEqual(len(warn), 1) + self.assertRegex(warn[0].filename, '.*test_overrides_gtk.*') + + # Validate overridden base with overridden sub-class. + self.assertEqual(Gtk.MessageDialog, gi.overrides.Gtk.MessageDialog) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL) + self.assertEqual(len(warn), 1) + self.assertRegex(warn[0].filename, '.*test_overrides_gtk.*') + + # Validate overridden base with non-overridden sub-class. + self.assertEqual(Gtk.AboutDialog, gi.repository.Gtk.AboutDialog) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + Gtk.AboutDialog(flags=Gtk.DialogFlags.MODAL) + self.assertEqual(len(warn), 1) + self.assertRegex(warn[0].filename, '.*test_overrides_gtk.*') + + def test_dialog_add_buttons(self): + if not GTK4: + # The overloaded "buttons" keyword gives a warning when attempting + # to use it for adding buttons as was available in PyGTK. + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + dialog = Gtk.Dialog(title='Foo', modal=True, + buttons=('test-button1', 1)) + self.assertEqual(len(warn), 1) + self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning)) + self.assertRegex(str(warn[0].message), + '.*ButtonsType.*add_buttons.*') + else: + dialog = Gtk.Dialog() + dialog.add_buttons('test-button1', 1) + + dialog.add_buttons('test-button2', 2, 'gtk-close', Gtk.ResponseType.CLOSE) button = dialog.get_widget_for_response(1) self.assertEqual('test-button1', button.get_label()) button = dialog.get_widget_for_response(2) self.assertEqual('test-button2', button.get_label()) button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE) - self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) + self.assertEqual('gtk-close', button.get_label()) + + with pytest.raises(ValueError, match="even number"): + dialog.add_buttons('test-button2', 2, 'gtk-close') - # Gtk.AboutDialog + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_dialog_deprecated_attributes(self): + dialog = Gtk.Dialog() + assert dialog.action_area == dialog.get_action_area() + assert dialog.vbox == dialog.get_content_area() + + def test_about_dialog(self): dialog = Gtk.AboutDialog() - self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) - # Gtk.MessageDialog + if not GTK4: + self.assertTrue(isinstance(dialog, Gtk.Dialog)) + + # AboutDialog is not sub-classed in overrides, make sure + # the mro still injects the base class "add_buttons" override. + self.assertTrue(hasattr(dialog, 'add_buttons')) + + def test_message_dialog(self): dialog = Gtk.MessageDialog(title='message dialog test', - flags=Gtk.DialogFlags.MODAL, + modal=True, buttons=Gtk.ButtonsType.OK, - message_format='dude!') + text='dude!') self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) @@ -237,66 +510,58 @@ class TestGtk(unittest.TestCase): text = dialog.get_property('text') self.assertEqual('dude!', text) - dialog.format_secondary_text('2nd text') - self.assertEqual(dialog.get_property('secondary-text'), '2nd text') - self.assertFalse(dialog.get_property('secondary-use-markup')) + if not GTK4: + dialog.format_secondary_text('2nd text') + self.assertEqual(dialog.get_property('secondary-text'), '2nd text') + self.assertFalse(dialog.get_property('secondary-use-markup')) - dialog.format_secondary_markup('2nd markup') - self.assertEqual(dialog.get_property('secondary-text'), '2nd markup') - self.assertTrue(dialog.get_property('secondary-use-markup')) + dialog.format_secondary_markup('2nd markup') + self.assertEqual(dialog.get_property('secondary-text'), '2nd markup') + self.assertTrue(dialog.get_property('secondary-use-markup')) - # Gtk.ColorSelectionDialog - dialog = Gtk.ColorSelectionDialog("color selection dialog test") + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_color_selection_dialog(self): + dialog = Gtk.ColorSelectionDialog(title="color selection dialog test") self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) self.assertEqual('color selection dialog test', dialog.get_title()) - # Gtk.FileChooserDialog + def test_file_chooser_dialog(self): # might cause a GVFS warning, do not break on this - old_mask = GLib.log_set_always_fatal( - GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR) - try: + with capture_glib_warnings(allow_warnings=True): dialog = Gtk.FileChooserDialog(title='file chooser dialog test', - buttons=('test-button1', 1), action=Gtk.FileChooserAction.SAVE) - finally: - GLib.log_set_always_fatal(old_mask) + self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) - - dialog.add_buttons('test-button2', 2, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) self.assertEqual('file chooser dialog test', dialog.get_title()) - button = dialog.get_widget_for_response(1) - self.assertEqual('test-button1', button.get_label()) - button = dialog.get_widget_for_response(2) - self.assertEqual('test-button2', button.get_label()) - button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE) - self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) + action = dialog.get_property('action') self.assertEqual(Gtk.FileChooserAction.SAVE, action) - # Gtk.FontSelectionDialog - dialog = Gtk.ColorSelectionDialog("font selection dialog test") + def test_file_chooser_dialog_default_action(self): + # might cause a GVFS warning, do not break on this + with capture_glib_warnings(allow_warnings=True): + dialog = Gtk.FileChooserDialog(title='file chooser dialog test') + + action = dialog.get_property('action') + self.assertEqual(Gtk.FileChooserAction.OPEN, action) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_font_selection_dialog(self): + dialog = Gtk.FontSelectionDialog(title="font selection dialog test") self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) self.assertEqual('font selection dialog test', dialog.get_title()) - # Gtk.RecentChooserDialog + @unittest.skipIf(GTK4, "not in gtk4") + def test_recent_chooser_dialog(self): test_manager = Gtk.RecentManager() dialog = Gtk.RecentChooserDialog(title='recent chooser dialog test', - buttons=('test-button1', 1), - manager=test_manager) + recent_manager=test_manager) self.assertTrue(isinstance(dialog, Gtk.Dialog)) self.assertTrue(isinstance(dialog, Gtk.Window)) - - dialog.add_buttons('test-button2', 2, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) self.assertEqual('recent chooser dialog test', dialog.get_title()) - button = dialog.get_widget_for_response(1) - self.assertEqual('test-button1', button.get_label()) - button = dialog.get_widget_for_response(2) - self.assertEqual('test-button2', button.get_label()) - button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE) - self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) class TestClass(GObject.GObject): __gtype_name__ = "GIOverrideTreeAPITest" @@ -312,29 +577,37 @@ class TestGtk(unittest.TestCase): self.tester.assertEqual(string_value, self.string_value) def test_buttons(self): - self.assertEqual(Gtk.Button, gi.overrides.Gtk.Button) + if not GTK4: + self.assertEqual(Gtk.Button, gi.overrides.Gtk.Button) # test Gtk.Button button = Gtk.Button() self.assertTrue(isinstance(button, Gtk.Button)) - self.assertTrue(isinstance(button, Gtk.Container)) + if Gtk._version != "4.0": + self.assertTrue(isinstance(button, Gtk.Container)) self.assertTrue(isinstance(button, Gtk.Widget)) - button = Gtk.Button(stock=Gtk.STOCK_CLOSE) - self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) - self.assertTrue(button.get_use_stock()) - self.assertTrue(button.get_use_underline()) - # test Gtk.Button use_stock - button = Gtk.Button(label=Gtk.STOCK_CLOSE, use_stock=True, use_underline=True) - self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) - self.assertTrue(button.get_use_stock()) - self.assertTrue(button.get_use_underline()) + if Gtk_version != "4.0": + # Using stock items causes hard warning in devel versions of GTK. + with capture_glib_warnings(allow_warnings=True): + button = Gtk.Button.new_from_stock(Gtk.STOCK_CLOSE) + + self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) + self.assertTrue(button.get_use_stock()) + self.assertTrue(button.get_use_underline()) + + # test Gtk.Button use_stock + button = Gtk.Button(label=Gtk.STOCK_CLOSE, use_stock=True, + use_underline=True) + self.assertEqual(Gtk.STOCK_CLOSE, button.get_label()) + self.assertTrue(button.get_use_stock()) + self.assertTrue(button.get_use_underline()) # test Gtk.LinkButton - self.assertRaises(TypeError, Gtk.LinkButton) - button = Gtk.LinkButton('http://www.Gtk.org', 'Gtk') + button = Gtk.LinkButton(uri='http://www.Gtk.org', label='Gtk') self.assertTrue(isinstance(button, Gtk.Button)) - self.assertTrue(isinstance(button, Gtk.Container)) + if Gtk._version != "4.0": + self.assertTrue(isinstance(button, Gtk.Container)) self.assertTrue(isinstance(button, Gtk.Widget)) self.assertEqual('http://www.Gtk.org', button.get_uri()) self.assertEqual('Gtk', button.get_label()) @@ -353,7 +626,7 @@ class TestGtk(unittest.TestCase): if isinstance(info, gi.types.ObjectInfo): classes = list(info.get_interfaces()) parent = info.get_parent() - while parent.get_name() != "Object": + while parent is not None and parent.get_name() != "Object": classes.append(parent) parent = parent.get_parent() classes = [kl for kl in classes if kl.get_namespace() == "Gtk"] @@ -392,43 +665,42 @@ class TestGtk(unittest.TestCase): self.assertEqual(adjustment.get_page_size(), page_size) def test_adjustment(self): - adjustment = Gtk.Adjustment(1, 0, 6, 4, 5, 3) - self.adjustment_check(adjustment, 1, 0, 6, 4, 5, 3) + adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, step_increment=4, page_increment=5, page_size=3) + self.adjustment_check(adjustment, value=1, lower=0, upper=6, step_increment=4, page_increment=5, page_size=3) - adjustment = Gtk.Adjustment(1, 0, 6, 4, 5) - self.adjustment_check(adjustment, 1, 0, 6, 4, 5) + adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, step_increment=4, page_increment=5) + self.adjustment_check(adjustment, value=1, lower=0, upper=6, step_increment=4, page_increment=5) - adjustment = Gtk.Adjustment(1, 0, 6, 4) - self.adjustment_check(adjustment, 1, 0, 6, 4) + adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, step_increment=4) + self.adjustment_check(adjustment, value=1, lower=0, upper=6, step_increment=4) - adjustment = Gtk.Adjustment(1, 0, 6) - self.adjustment_check(adjustment, 1, 0, 6) + adjustment = Gtk.Adjustment(value=1, lower=0, upper=6) + self.adjustment_check(adjustment, value=1, lower=0, upper=6) adjustment = Gtk.Adjustment() self.adjustment_check(adjustment) - adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, - step_increment=4, page_increment=5, page_size=3) - self.adjustment_check(adjustment, 1, 0, 6, 4, 5, 3) + if not GTK4: + adjustment = Gtk.Adjustment(1, -1, 3, 0, 0, 0) + self.adjustment_check(adjustment, value=1, lower=-1, upper=3) + adjustment = Gtk.Adjustment(1, -1, 3, 0, 0, 0, value=2) + self.adjustment_check(adjustment, value=2, lower=-1, upper=3) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_table(self): table = Gtk.Table() self.assertTrue(isinstance(table, Gtk.Table)) - self.assertTrue(isinstance(table, Gtk.Container)) + if Gtk._version != "4.0": + self.assertTrue(isinstance(table, Gtk.Container)) self.assertTrue(isinstance(table, Gtk.Widget)) self.assertEqual(table.get_size(), (1, 1)) self.assertEqual(table.get_homogeneous(), False) - table = Gtk.Table(2, 3) + + table = Gtk.Table(n_rows=2, n_columns=3) self.assertEqual(table.get_size(), (2, 3)) self.assertEqual(table.get_homogeneous(), False) - table = Gtk.Table(2, 3, True) - self.assertEqual(table.get_size(), (2, 3)) - self.assertEqual(table.get_homogeneous(), True) - # Test PyGTK interface - table = Gtk.Table(rows=3, columns=2) - self.assertEqual(table.get_size(), (3, 2)) - # Test using the actual property names table = Gtk.Table(n_rows=2, n_columns=3, homogeneous=True) self.assertEqual(table.get_size(), (2, 3)) self.assertEqual(table.get_homogeneous(), True) @@ -441,13 +713,15 @@ class TestGtk(unittest.TestCase): def test_scrolledwindow(self): sw = Gtk.ScrolledWindow() self.assertTrue(isinstance(sw, Gtk.ScrolledWindow)) - self.assertTrue(isinstance(sw, Gtk.Container)) + if Gtk._version != "4.0": + self.assertTrue(isinstance(sw, Gtk.Container)) self.assertTrue(isinstance(sw, Gtk.Widget)) sb = sw.get_hscrollbar() self.assertEqual(sw.get_hadjustment(), sb.get_adjustment()) sb = sw.get_vscrollbar() self.assertEqual(sw.get_vadjustment(), sb.get_adjustment()) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_widget_drag_methods(self): widget = Gtk.Button() @@ -464,6 +738,8 @@ class TestGtk(unittest.TestCase): widget.drag_dest_get_track_motion() widget.drag_dest_set_track_motion(True) widget.drag_dest_get_target_list() + widget.drag_dest_set_target_list(None) + widget.drag_dest_set_target_list(Gtk.TargetList.new([Gtk.TargetEntry.new('test', 0, 0)])) widget.drag_dest_unset() @@ -475,10 +751,11 @@ class TestGtk(unittest.TestCase): widget.drag_source_add_image_targets() widget.drag_source_add_text_targets() widget.drag_source_add_uri_targets() - widget.drag_source_set_icon_name("") + widget.drag_source_set_icon_name("_About") widget.drag_source_set_icon_pixbuf(GdkPixbuf.Pixbuf()) - widget.drag_source_set_icon_stock("") + widget.drag_source_set_icon_stock(Gtk.STOCK_ABOUT) widget.drag_source_get_target_list() + widget.drag_source_set_target_list(None) widget.drag_source_set_target_list(Gtk.TargetList.new([Gtk.TargetEntry.new('test', 0, 0)])) widget.drag_source_unset() @@ -487,7 +764,30 @@ class TestGtk(unittest.TestCase): self.assertTrue(hasattr(widget, 'drag_dest_set_proxy')) self.assertTrue(hasattr(widget, 'drag_get_data')) - def test_drag_target_list(self): + @unittest.skipIf(Gtk_version != "4.0", "gtk4 only") + def test_widget_drag_methods_gtk4(self): + widget = Gtk.Button() + widget.drag_check_threshold(0, 0, 0, 0) + + # drag source + drag_source = Gtk.DragSource() + content = Gdk.ContentProvider.new_for_value("data") + drag_source.set_content(content) + drag_source.set_actions(Gdk.DragAction.COPY) + image = Gtk.Image.new_from_icon_name("dialog-warning") + drag_source.set_icon(image.get_paintable(), 0, 0) + widget.add_controller(drag_source) + + # drop target + drop_target = Gtk.DropTarget.new(Gdk.ContentFormats.new([]), Gdk.DragAction.COPY) + widget.add_controller(drop_target) + + widget.remove_controller(drag_source) + widget.remove_controller(drop_target) + + @unittest.skipIf(sys.platform == "darwin", "crashes") + @unittest.skipIf(GTK4, "uses lots of gtk3 only api") + def test_tree_view_drag_target_list_gtk3(self): mixed_target_list = [Gtk.TargetEntry.new('test0', 0, 0), ('test1', 1, 1), Gtk.TargetEntry.new('test2', 2, 2), @@ -519,8 +819,21 @@ class TestGtk(unittest.TestCase): treeview.enable_model_drag_dest(mixed_target_list, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) + @unittest.skipUnless(GTK4, "gtk4 only") + def test_tree_view_drag_content_formats_gtk4(self): + content_formats = Gdk.ContentFormats.new( + ["application/json", "GTK_TREE_MODEL_ROW"] + ) + treeview = Gtk.TreeView() + treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, + content_formats, + Gdk.DragAction.MOVE) + + treeview.enable_model_drag_dest(content_formats, + Gdk.DragAction.MOVE) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_scrollbar(self): - # PyGTK compat adjustment = Gtk.Adjustment() hscrollbar = Gtk.HScrollbar() @@ -528,8 +841,8 @@ class TestGtk(unittest.TestCase): self.assertNotEqual(hscrollbar.props.adjustment, adjustment) self.assertNotEqual(vscrollbar.props.adjustment, adjustment) - hscrollbar = Gtk.HScrollbar(adjustment) - vscrollbar = Gtk.VScrollbar(adjustment) + hscrollbar = Gtk.HScrollbar(adjustment=adjustment) + vscrollbar = Gtk.VScrollbar(adjustment=adjustment) self.assertEqual(hscrollbar.props.adjustment, adjustment) self.assertEqual(vscrollbar.props.adjustment, adjustment) @@ -539,31 +852,38 @@ class TestGtk(unittest.TestCase): self.assertEqual(iconview.props.model, None) model = Gtk.ListStore(str) - iconview = Gtk.IconView(model) + iconview = Gtk.IconView(model=model) self.assertEqual(iconview.props.model, model) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_toolbutton(self): # PyGTK compat - button = Gtk.ToolButton() - self.assertEqual(button.props.stock_id, None) - button = Gtk.ToolButton('gtk-new') - self.assertEqual(button.props.stock_id, 'gtk-new') + # Using stock items causes hard warning in devel versions of GTK. + with capture_glib_warnings(allow_warnings=True): + button = Gtk.ToolButton() + self.assertEqual(button.props.stock_id, None) - icon = Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.SMALL_TOOLBAR) + button = Gtk.ToolButton(stock_id='gtk-new') + self.assertEqual(button.props.stock_id, 'gtk-new') + icon = Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.SMALL_TOOLBAR) button = Gtk.ToolButton(label='mylabel', icon_widget=icon) self.assertEqual(button.props.label, 'mylabel') self.assertEqual(button.props.icon_widget, icon) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_iconset(self): - # PyGTK compat Gtk.IconSet() pixbuf = GdkPixbuf.Pixbuf() - Gtk.IconSet(pixbuf) + Gtk.IconSet.new_from_pixbuf(pixbuf) + + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + Gtk.IconSet(pixbuf) + assert issubclass(warn[0].category, PyGTKDeprecationWarning) def test_viewport(self): - # PyGTK compat vadjustment = Gtk.Adjustment() hadjustment = Gtk.Adjustment() @@ -573,55 +893,169 @@ class TestGtk(unittest.TestCase): self.assertEqual(viewport.props.vadjustment, vadjustment) self.assertEqual(viewport.props.hadjustment, hadjustment) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_stock_lookup(self): - l = Gtk.stock_lookup('gtk-ok') - self.assertEqual(type(l), Gtk.StockItem) - self.assertEqual(l.stock_id, 'gtk-ok') + stock_item = Gtk.stock_lookup('gtk-ok') + self.assertEqual(type(stock_item), Gtk.StockItem) + self.assertEqual(stock_item.stock_id, 'gtk-ok') self.assertEqual(Gtk.stock_lookup('nosuchthing'), None) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_gtk_main(self): # with no arguments - GLib.timeout_add(100, Gtk.main_quit) + GLib.idle_add(Gtk.main_quit) Gtk.main() # overridden function ignores its arguments - GLib.timeout_add(100, Gtk.main_quit, 'hello') + GLib.idle_add(Gtk.main_quit, 'hello') Gtk.main() + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_widget_render_icon(self): + button = Gtk.Button(label='OK') + pixbuf = button.render_icon(Gtk.STOCK_OK, Gtk.IconSize.BUTTON) + self.assertTrue(pixbuf is not None) + + @unittest.skipUnless( + Gtk_version == "4.0", "GtkWidget prior to gtk4 is not iterable") + def test_widget_iterable(self): + widget = Gtk.Box() + + children = [child for child in widget] + self.assertEqual(len(children), 0) + + nr_children = 7 + for i in range(nr_children): + widget.append(Gtk.Label()) + + children = [child for child in widget] + self.assertEqual(len(children), nr_children) + + first_child = children[0] + last_child = children[-1] + self.assertEqual(first_child, widget.get_first_child()) + self.assertEqual(last_child, widget.get_last_child()) + + self.assertTrue(first_child in widget) + self.assertTrue(last_child in widget) + + widget.remove(first_child) + self.assertTrue(first_child not in widget) + self.assertTrue(last_child in widget) + + widget.remove(last_child) + self.assertTrue(first_child not in widget) + self.assertTrue(last_child not in widget) + @unittest.skipUnless(Gtk, 'Gtk not available') -class TestSignals(unittest.TestCase): - class WindowWithSizeAllocOverride(Gtk.ScrolledWindow): - __gsignals__ = {'size-allocate': 'override'} +class TestWidget(unittest.TestCase): + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_style_get_property_gvalue(self): + button = Gtk.Button() + value = GObject.Value(int, -42) + button.style_get_property('focus-padding', value) + # Test only that the style property changed since we can't actuall + # set it. + self.assertNotEqual(value.get_int(), -42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_style_get_property_return_with_explicit_gvalue(self): + button = Gtk.Button() + value = GObject.Value(int, -42) + result = button.style_get_property('focus-padding', value) + self.assertIsInstance(result, int) + self.assertNotEqual(result, -42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_style_get_property_return_with_implicit_gvalue(self): + button = Gtk.Button() + result = button.style_get_property('focus-padding') + self.assertIsInstance(result, int) + self.assertNotEqual(result, -42) - def __init__(self): - Gtk.ScrolledWindow.__init__(self) - self._alloc_called = False - self._alloc_value = None - self._alloc_error = None + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_style_get_property_error(self): + button = Gtk.Button() + with self.assertRaises(ValueError): + button.style_get_property('not-a-valid-style-property') - def do_size_allocate(self, alloc): - self._alloc_called = True - self._alloc_value = alloc + @unittest.skipIf(Gtk_version != "4.0", "only in gtk4") + def test_translate_coordinates(self): + box = Gtk.Box() + child = Gtk.Button() + box.append(child) + with realized(box): + assert box.translate_coordinates(child, 42, 24) == (42.0, 24.0) + assert box.translate_coordinates(Gtk.Button(), 0, 0) is None - try: - Gtk.ScrolledWindow.do_size_allocate(self, alloc) - except Exception as e: - self._alloc_error = e +@unittest.skipIf(sys.platform == "darwin", "hangs") +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestSignals(unittest.TestCase): def test_class_closure_override_with_aliased_type(self): - win = self.WindowWithSizeAllocOverride() + class WindowWithSizeAllocOverride(Gtk.ScrolledWindow): + if not GTK4: + __gsignals__ = {'size-allocate': 'override'} + + def __init__(self): + Gtk.ScrolledWindow.__init__(self) + self._alloc_called = False + self._alloc_value = None + self._alloc_error = None + + def do_size_allocate(self, *args): + self._alloc_called = True + self._alloc_value = args[0] + + try: + Gtk.ScrolledWindow.do_size_allocate(self, *args) + except Exception as e: + self._alloc_error = e + + win = WindowWithSizeAllocOverride() rect = Gdk.Rectangle() rect.width = 100 rect.height = 100 with realized(win): win.show() - win.size_allocate(rect) + win.get_preferred_size() + if GTK4: + win.size_allocate(rect, 0) + else: + win.size_allocate(rect) self.assertTrue(win._alloc_called) - self.assertIsInstance(win._alloc_value, Gdk.Rectangle) + if GTK4: + self.assertIsInstance(win._alloc_value, int) + else: + self.assertIsInstance(win._alloc_value, Gdk.Rectangle) self.assertTrue(win._alloc_error is None, win._alloc_error) + @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=735693 + def test_overlay_child_position(self): + def get_child_position(overlay, widget, rect, user_data=None): + rect.x = 1 + rect.y = 2 + rect.width = 3 + rect.height = 4 + return True + + overlay = Gtk.Overlay() + overlay.connect('get-child-position', get_child_position) + + rect = Gdk.Rectangle() + rect.x = -1 + rect.y = -1 + rect.width = -1 + rect.height = -1 + + overlay.emit('get-child-position', None, rect) + self.assertEqual(rect.x, 1) + self.assertEqual(rect.y, 2) + self.assertEqual(rect.width, 3) + self.assertEqual(rect.height, 4) + @unittest.skipUnless(Gtk, 'Gtk not available') class TestBuilder(unittest.TestCase): @@ -633,6 +1067,40 @@ class TestBuilder(unittest.TestCase): []), } + class SignalTestObject(GObject.GObject): + __gtype_name__ = "GIOverrideSignalTestObject" + + def test_add_from_string(self): + builder = Gtk.Builder() + + with pytest.raises(TypeError): + builder.add_from_string(object()) + + builder.add_from_string(u"") + builder.add_from_string("") + + def get_example(string): + return u"""\ +<interface> + <menu id="appmenu"> + <section> + <item> + <attribute name="label">%s</attribute> + </item> + </section> + </menu> +</interface>""" % string + + builder.add_from_string(get_example(u"ä" * 1000)) + + builder = Gtk.Builder() + builder.add_objects_from_string(u"", ['']) + builder.add_objects_from_string("", ['']) + builder.add_objects_from_string(get_example(u"ä" * 1000), ['']) + + with pytest.raises(TypeError): + builder.add_objects_from_string(object(), []) + def test_extract_handler_and_args_object(self): class Obj(): pass @@ -640,32 +1108,51 @@ class TestBuilder(unittest.TestCase): obj = Obj() obj.foo = lambda: None - handler, args = Gtk.Builder._extract_handler_and_args(obj, 'foo') + handler, args = Gtk._extract_handler_and_args(obj, 'foo') self.assertEqual(handler, obj.foo) self.assertEqual(len(args), 0) def test_extract_handler_and_args_dict(self): obj = {'foo': lambda: None} - handler, args = Gtk.Builder._extract_handler_and_args(obj, 'foo') + handler, args = Gtk._extract_handler_and_args(obj, 'foo') self.assertEqual(handler, obj['foo']) self.assertEqual(len(args), 0) def test_extract_handler_and_args_with_seq(self): obj = {'foo': (lambda: None, 1, 2)} - handler, args = Gtk.Builder._extract_handler_and_args(obj, 'foo') + handler, args = Gtk._extract_handler_and_args(obj, 'foo') self.assertEqual(handler, obj['foo'][0]) self.assertSequenceEqual(args, [1, 2]) def test_extract_handler_and_args_no_handler_error(self): obj = dict(foo=lambda: None) self.assertRaises(AttributeError, - Gtk.Builder._extract_handler_and_args, + Gtk._extract_handler_and_args, obj, 'not_a_handler') + def test_extract_handler_and_args_type_mismatch(self): + with pytest.raises(TypeError): + Gtk._extract_handler_and_args({"foo": object()}, "foo") + + with pytest.raises(TypeError): + Gtk._extract_handler_and_args({"foo": []}, "foo") + def test_builder_with_handler_and_args(self): - builder = Gtk.Builder() + args_collector = [] + + def on_signal(*args): + args_collector.append(args) + + signals_dict = {'on_signal1': (on_signal, 1, 2), + 'on_signal2': on_signal} + + if GTK4: + builder = Gtk.Builder(signals_dict) + else: + builder = Gtk.Builder() + builder.add_from_string(""" <interface> <object class="GIOverrideSignalTest" id="object_sig_test"> @@ -675,13 +1162,8 @@ class TestBuilder(unittest.TestCase): </interface> """) - args_collector = [] - - def on_signal(*args): - args_collector.append(args) - - builder.connect_signals({'on_signal1': (on_signal, 1, 2), - 'on_signal2': on_signal}) + if not GTK4: + builder.connect_signals(signals_dict) objects = builder.get_objects() self.assertEqual(len(objects), 1) @@ -692,6 +1174,40 @@ class TestBuilder(unittest.TestCase): self.assertSequenceEqual(args_collector[0], (obj, 1, 2)) self.assertSequenceEqual(args_collector[1], (obj, )) + def test_builder_with_handler_object(self): + args_collector = [] + + def on_signal(*args): + args_collector.append(args) + + signals_dict = {'on_signal1': (on_signal, 1, 2), + 'on_signal2': on_signal} + + if GTK4: + builder = Gtk.Builder(signals_dict) + else: + builder = Gtk.Builder() + + builder.add_from_string(""" + <interface> + <object class="GIOverrideSignalTestObject" id="foo"/> + <object class="GIOverrideSignalTest" id="object_sig_test"> + <signal name="test-signal" handler="on_signal1" object="foo" swapped="no"/> + <signal name="test-signal" handler="on_signal2" after="yes" object="foo" swapped="no"/> + </object> + </interface> + """) + + if not GTK4: + builder.connect_signals(signals_dict) + + obj = builder.get_object("foo") + emitter = builder.get_object("object_sig_test") + emitter.emit("test-signal") + assert len(args_collector) == 2 + assert args_collector[0] == (obj, 1, 2) + assert args_collector[1] == (obj, ) + def test_builder(self): self.assertEqual(Gtk.Builder, gi.overrides.Gtk.Builder) @@ -712,7 +1228,11 @@ class TestBuilder(unittest.TestCase): self.after_sentinel += 1 signal_checker = SignalCheck() - builder = Gtk.Builder() + signal_checker = SignalCheck() + if GTK4: + builder = Gtk.Builder(signal_checker) + else: + builder = Gtk.Builder() # add object1 to the builder builder.add_from_string(""" @@ -739,8 +1259,9 @@ class TestBuilder(unittest.TestCase): </interface> """, ['object3']) - # hook up signals - builder.connect_signals(signal_checker) + # hook up signals for Gtk3 + if not GTK4: + builder.connect_signals(signal_checker) # call their notify signals and check sentinel objects = builder.get_objects() @@ -753,12 +1274,161 @@ class TestBuilder(unittest.TestCase): @unittest.skipUnless(Gtk, 'Gtk not available') +class TestTreeModelRow(unittest.TestCase): + def test_tree_model_row(self): + model = Gtk.TreeStore(int) + iter_ = model.append(None, [42]) + path = model.get_path(iter_) + Gtk.TreeModelRow(model, iter_) + row = Gtk.TreeModelRow(model, path) + + with pytest.raises(TypeError): + row["foo"] + + with pytest.raises(TypeError): + Gtk.TreeModelRow(model, 42) + + with pytest.raises(TypeError): + Gtk.TreeModelRow(42, path) + + iter_ = model.append(None, [24]) + row = Gtk.TreeModelRow(model, iter_) + assert row.previous[0] == 42 + assert row.get_previous()[0] == 42 + assert row.previous.previous is None + + +@unittest.skipUnless(Gtk, "Gtk not available") +class TestCustomSorter(): + class Person(GObject.GObject): + + name = GObject.Property(type=str, default="") + + def __init__(self, name): + super().__init__() + self.props.name = name + + user_data = "user_data" + + def names_sort(self, name_a, name_b, user_data): + assert user_data is None + if name_a.props.name < name_b.props.name: + return Gtk.Ordering.SMALLER + elif name_a.props.name > name_b.props.name: + return Gtk.Ordering.LARGER + else: + return Gtk.Ordering.EQUAL + + def names_invert_sort(self, name_a, name_b, user_data): + assert user_data == self.user_data + if name_a.props.name < name_b.props.name: + return Gtk.Ordering.LARGER + elif name_a.props.name > name_b.props.name: + return Gtk.Ordering.SMALLER + else: + return Gtk.Ordering.EQUAL + + @unittest.skipIf(Gtk_version != "4.0", "gtk4 only") + def test_custom_sorter_init(self): + custom_sorter_empty = Gtk.CustomSorter() + assert isinstance(custom_sorter_empty, Gtk.CustomSorter) + + custom_sorter_empty.set_sort_func(self.names_sort) + assert isinstance(custom_sorter_empty, Gtk.CustomSorter) + + custom_sorter_empty.set_sort_func( + self.names_invert_sort, self.user_data) + assert isinstance(custom_sorter_empty, Gtk.CustomSorter) + + custom_sorter_empty_sort = Gtk.CustomSorter.new(None) + assert isinstance(custom_sorter_empty_sort, Gtk.CustomSorter) + + custom_sorter_with_sort = Gtk.CustomSorter.new(self.names_sort, None) + assert isinstance(custom_sorter_with_sort, Gtk.CustomSorter) + + custom_sorter_with_sort_ud = Gtk.CustomSorter.new( + self.names_invert_sort, self.user_data) + assert isinstance(custom_sorter_with_sort_ud, Gtk.CustomSorter) + + @unittest.skipIf(Gtk_version != "4.0", "gtk4 only") + def test_custom_sorter_with_model(self): + model = Gio.ListStore.new(self.Person) + sort_model = Gtk.SortListModel.new(model) + assert sort_model.props.sorter is None + + empty_sorter = Gtk.CustomSorter() + empty_sorter.set_sort_func(self.names_sort, None) + sort_model.set_sorter(empty_sorter) + assert sort_model.props.sorter is empty_sorter + + john = self.Person("john") + bob = self.Person("bob") + model.append(john) + model.append(bob) + assert sort_model[0] == bob + assert sort_model[1] == john + + alice = self.Person("alice") + model.append(alice) + assert sort_model[0] == alice + assert sort_model[2] == john + + new_model = Gio.ListStore.new(self.Person) + new_sort_model = Gtk.SortListModel.new(new_model) + assert new_sort_model.props.sorter is None + + invert_sorter = Gtk.CustomSorter.new( + self.names_invert_sort, self.user_data) + new_sort_model.set_sorter(invert_sorter) + assert new_sort_model.props.sorter is invert_sorter + + beatles = ["john", "paul", "george", "ringo"] + for member in beatles: + new_model.append(self.Person(member)) + + expected_result = ["ringo", "paul", "john", "george"] + for result, member in zip(new_sort_model, expected_result): + assert result.props.name == member + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestListStore(unittest.TestCase): + + def test_insert_with_values(self): + model = Gtk.ListStore(int) + assert hasattr(model, 'insert_with_values') + iter_ = model.insert_with_values(0, (0,), [42]) + assert isinstance(iter_, Gtk.TreeIter) + assert hasattr(model, 'insert_with_valuesv') + iter_ = model.insert_with_valuesv(0, (0,), [43]) + assert isinstance(iter_, Gtk.TreeIter) + assert len(model) == 2 + + +@ignore_gi_deprecation_warnings +@unittest.skipUnless(Gtk, 'Gtk not available') class TestTreeModel(unittest.TestCase): + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_tree_model_sort_new_with_model_old(self): + # https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/1134 + model = Gtk.TreeStore(int) + sort_model = model.sort_new_with_model() + assert isinstance(sort_model, Gtk.TreeModelSort) + assert sort_model.get_model() == model + + def test_tree_model_sort_new_with_model_new(self): + # https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/1134 + model = Gtk.TreeStore(int) + sort_model = Gtk.TreeModelSort.new_with_model(child_model=model) + assert isinstance(sort_model, Gtk.TreeModelSort) + assert sort_model.get_model() == model + def test_tree_model_sort(self): - self.assertEqual(Gtk.TreeModelSort, gi.overrides.Gtk.TreeModelSort) - self.assertRaises(TypeError, Gtk.TreeModelSort) + if not GTK4: + self.assertEqual(Gtk.TreeModelSort, gi.overrides.Gtk.TreeModelSort) model = Gtk.TreeStore(int, bool) - model_sort = Gtk.TreeModelSort(model) + model_sort = Gtk.TreeModelSort(model=model) self.assertEqual(model_sort.get_model(), model) def test_tree_store(self): @@ -803,11 +1473,11 @@ class TestTreeModel(unittest.TestCase): i % 2, bool(i % 2), i, - GObject.G_MAXULONG, - GObject.G_MININT64, + GLib.MAXULONG, + GLib.MININT64, 0xffffffffffffffff, 254, - _bytes('a') + b'a' )) # test set parent = tree_store.append(parent) @@ -825,11 +1495,11 @@ class TestTreeModel(unittest.TestCase): 7, i % 2, 8, bool(i % 2), 9, i, - 10, GObject.G_MAXULONG, - 11, GObject.G_MININT64, + 10, GLib.MAXULONG, + 11, GLib.MININT64, 12, 0xffffffffffffffff, 13, 254, - 14, _bytes('a')) + 14, b'a') parent = tree_store.append(parent) i = 98 @@ -845,11 +1515,11 @@ class TestTreeModel(unittest.TestCase): 7: i % 2, 8: bool(i % 2), 9: i, - 10: GObject.G_MAXULONG, - 11: GObject.G_MININT64, + 10: GLib.MAXULONG, + 11: GLib.MININT64, 12: 0xffffffffffffffff, 13: 254, - 14: _bytes('a')}) + 14: b'a'}) parent = tree_store.append(parent) i = 99 @@ -866,11 +1536,11 @@ class TestTreeModel(unittest.TestCase): i % 2, bool(i % 2), i, - GObject.G_MAXULONG, - GObject.G_MININT64, + GLib.MAXULONG, + GLib.MININT64, 0xffffffffffffffff, 254, - _bytes('a'))) + b'a')) # len gets the number of children in the root node # since we kept appending to the previous node @@ -906,9 +1576,9 @@ class TestTreeModel(unittest.TestCase): uint_ = tree_store.get_value(treeiter, 9) self.assertEqual(uint_, i) ulong_ = tree_store.get_value(treeiter, 10) - self.assertEqual(ulong_, GObject.G_MAXULONG) + self.assertEqual(ulong_, GLib.MAXULONG) int64_ = tree_store.get_value(treeiter, 11) - self.assertEqual(int64_, GObject.G_MININT64) + self.assertEqual(int64_, GLib.MININT64) uint64_ = tree_store.get_value(treeiter, 12) self.assertEqual(uint64_, 0xffffffffffffffff) uchar_ = tree_store.get_value(treeiter, 13) @@ -958,6 +1628,12 @@ class TestTreeModel(unittest.TestCase): tree_store.insert(None, 1) self.assertEqual(signals, ['row-inserted']) + # One set one signal + signals.pop() + tree_iter = tree_store.append(None, (10, False)) + tree_store.set(tree_iter, (0, 1), (20, True)) + self.assertEqual(signals, ['row-inserted', 'row-changed']) + def test_list_store(self): class TestPyObject(object): pass @@ -980,7 +1656,7 @@ class TestTreeModel(unittest.TestCase): bool(i % 2))) i = 93 - label = _unicode('this is row #93') + label = u'this is row #93' treeiter = list_store.append() list_store.set_value(treeiter, 0, i) list_store.set_value(treeiter, 1, label) @@ -1004,7 +1680,7 @@ class TestTreeModel(unittest.TestCase): # test automatic unicode->str conversion i = 94 - label = _unicode('this is row #94') + label = u'this is row #94' treeiter = list_store.append((i, label, TestGtk.TestClass(self, i, label), @@ -1215,6 +1891,269 @@ class TestTreeModel(unittest.TestCase): list_store.insert(1) self.assertEqual(signals, ['row-inserted']) + # One set one signal + signals.pop() + tree_iter = list_store.append((10, False)) + list_store.set(tree_iter, (0, 1), (20, True)) + self.assertEqual(signals, ['row-inserted', 'row-changed']) + + def test_list_store_insert_before(self): + store = Gtk.ListStore(object) + signals = [] + + def on_row_inserted(store, tree_path, tree_iter, signal_list): + signal_list.append('row-inserted') + + def on_row_changed(store, tree_path, tree_iter, signal_list): + signal_list.append('row-changed') + + store.connect('row-inserted', on_row_inserted, signals) + store.connect('row-changed', on_row_changed, signals) + + iter_ = store.append([0]) + assert store.get_value(iter_, 0) == 0 + assert signals == ['row-inserted'] + del signals[:] + + # append empty + iter_ = store.insert_before(None) + assert store.get_path(iter_).get_indices() == [1] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # insert empty + iter_ = store.insert_before(iter_) + assert store.get_path(iter_).get_indices() == [1] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # append non-empty + iter_ = store.insert_before(None, [1234]) + assert store.get_path(iter_).get_indices() == [3] + assert store.get_value(iter_, 0) == 1234 + assert signals == ['row-inserted'] + del signals[:] + + # insert non-empty + iter_ = store.insert_before(iter_, [4321]) + assert store.get_path(iter_).get_indices() == [3] + assert store.get_value(iter_, 0) == 4321 + assert signals == ['row-inserted'] + del signals[:] + + assert [r[0] for r in store] == [0, None, None, 4321, 1234] + + def test_list_store_insert_after(self): + store = Gtk.ListStore(object) + signals = [] + + def on_row_inserted(store, tree_path, tree_iter, signal_list): + signal_list.append('row-inserted') + + def on_row_changed(store, tree_path, tree_iter, signal_list): + signal_list.append('row-changed') + + store.connect('row-inserted', on_row_inserted, signals) + store.connect('row-changed', on_row_changed, signals) + + iter_ = store.append([0]) + assert store.get_value(iter_, 0) == 0 + assert signals == ['row-inserted'] + del signals[:] + + # prepend empty + iter_ = store.insert_after(None) + assert store.get_path(iter_).get_indices() == [0] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # insert empty + iter_ = store.insert_after(iter_) + assert store.get_path(iter_).get_indices() == [1] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # prepend non-empty + iter_ = store.insert_after(None, [1234]) + assert store.get_path(iter_).get_indices() == [0] + assert store.get_value(iter_, 0) == 1234 + assert signals == ['row-inserted'] + del signals[:] + + # insert non-empty + iter_ = store.insert_after(iter_, [4321]) + assert store.get_path(iter_).get_indices() == [1] + assert store.get_value(iter_, 0) == 4321 + assert signals == ['row-inserted'] + del signals[:] + + assert [r[0] for r in store] == [1234, 4321, None, None, 0] + + def test_tree_store_insert_before(self): + store = Gtk.TreeStore(object) + signals = [] + + def on_row_inserted(store, tree_path, tree_iter, signal_list): + signal_list.append('row-inserted') + + def on_row_changed(store, tree_path, tree_iter, signal_list): + signal_list.append('row-changed') + + store.connect('row-inserted', on_row_inserted, signals) + store.connect('row-changed', on_row_changed, signals) + + parent = store.append(None, [-1]) + assert signals == ['row-inserted'] + del signals[:] + + iter_ = store.append(parent, [0]) + assert store.get_path(iter_).get_indices() == [0, 0] + assert store.get_value(iter_, 0) == 0 + assert signals == ['row-inserted'] + del signals[:] + + # append empty + iter_ = store.insert_before(parent, None) + assert store.get_path(iter_).get_indices() == [0, 1] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # insert empty + iter_ = store.insert_before(parent, iter_) + assert store.get_path(iter_).get_indices() == [0, 1] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # append non-empty + iter_ = store.insert_before(parent, None, [1234]) + assert store.get_path(iter_).get_indices() == [0, 3] + assert store.get_value(iter_, 0) == 1234 + assert signals == ['row-inserted'] + del signals[:] + + # insert non-empty + iter_ = store.insert_before(parent, iter_, [4321]) + assert store.get_path(iter_).get_indices() == [0, 3] + assert store.get_value(iter_, 0) == 4321 + assert signals == ['row-inserted'] + del signals[:] + + def func(model, path, iter_, rows): + rows.append((path.get_indices(), model[iter_][:])) + + rows = [] + store.foreach(func, rows) + assert rows == [ + ([0], [-1]), ([0, 0], [0]), ([0, 1], [None]), ([0, 2], [None]), + ([0, 3], [4321]), ([0, 4], [1234])] + + def test_tree_store_insert_before_none(self): + store = Gtk.TreeStore(object) + root = store.append(None) + sub = store.append(root) + + iter_ = store.insert_before(None, None, [1]) + assert store.get_path(iter_).get_indices() == [1] + + iter_ = store.insert_before(root, None, [1]) + assert store.get_path(iter_).get_indices() == [0, 1] + + iter_ = store.insert_before(sub, None, [1]) + assert store.get_path(iter_).get_indices() == [0, 0, 0] + + iter_ = store.insert_before(None, root, [1]) + assert store.get_path(iter_).get_indices() == [0] + + iter_ = store.insert_before(None, sub, [1]) + assert store.get_path(iter_).get_indices() == [1, 0] + + def test_tree_store_insert_after(self): + store = Gtk.TreeStore(object) + signals = [] + + def on_row_inserted(store, tree_path, tree_iter, signal_list): + signal_list.append('row-inserted') + + def on_row_changed(store, tree_path, tree_iter, signal_list): + signal_list.append('row-changed') + + store.connect('row-inserted', on_row_inserted, signals) + store.connect('row-changed', on_row_changed, signals) + + parent = store.append(None, [-1]) + assert signals == ['row-inserted'] + del signals[:] + + iter_ = store.append(parent, [0]) + assert store.get_path(iter_).get_indices() == [0, 0] + assert store.get_value(iter_, 0) == 0 + assert signals == ['row-inserted'] + del signals[:] + + # append empty + iter_ = store.insert_after(parent, None) + assert store.get_path(iter_).get_indices() == [0, 0] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # insert empty + iter_ = store.insert_after(parent, iter_) + assert store.get_path(iter_).get_indices() == [0, 1] + assert store.get_value(iter_, 0) is None + assert signals == ['row-inserted'] + del signals[:] + + # append non-empty + iter_ = store.insert_after(parent, None, [1234]) + assert store.get_path(iter_).get_indices() == [0, 0] + assert store.get_value(iter_, 0) == 1234 + assert signals == ['row-inserted'] + del signals[:] + + # insert non-empty + iter_ = store.insert_after(parent, iter_, [4321]) + assert store.get_path(iter_).get_indices() == [0, 1] + assert store.get_value(iter_, 0) == 4321 + assert signals == ['row-inserted'] + del signals[:] + + def func(model, path, iter_, rows): + rows.append((path.get_indices(), model[iter_][:])) + + rows = [] + store.foreach(func, rows) + + assert rows == [ + ([0], [-1]), ([0, 0], [1234]), ([0, 1], [4321]), + ([0, 2], [None]), ([0, 3], [None]), ([0, 4], [0])] + + def test_tree_store_insert_after_none(self): + store = Gtk.TreeStore(object) + root = store.append(None) + sub = store.append(root) + + iter_ = store.insert_after(None, None, [1]) + assert store.get_path(iter_).get_indices() == [0] + + iter_ = store.insert_after(root, None, [1]) + assert store.get_path(iter_).get_indices() == [1, 0] + + iter_ = store.insert_after(sub, None, [1]) + assert store.get_path(iter_).get_indices() == [1, 1, 0] + + iter_ = store.insert_after(None, root, [1]) + assert store.get_path(iter_).get_indices() == [2] + + iter_ = store.insert_after(None, sub, [1]) + assert store.get_path(iter_).get_indices() == [1, 2] + def test_tree_path(self): p1 = Gtk.TreePath() p2 = Gtk.TreePath.new_first() @@ -1248,6 +2187,10 @@ class TestTreeModel(unittest.TestCase): self.assertEqual(p1[2], 3) self.assertRaises(IndexError, p1.__getitem__, 3) + def test_tree_path_empty(self): + p1 = Gtk.TreePath.new() + assert str(p1) == "" + def test_tree_model(self): tree_store = Gtk.TreeStore(int, str) @@ -1487,6 +2430,52 @@ class TestTreeModel(unittest.TestCase): self.assertRaises(TypeError, set_row3) + def test_tree_row_sequence(self): + model = Gtk.ListStore(int, str, float) + model.append([1, "one", -0.1]) + + self.assertEqual([1, "one", -0.1], model[0][0, 1, 2]) + self.assertEqual([1, "one"], model[0][0, 1]) + self.assertEqual(["one", -0.1], model[0][1, 2]) + self.assertEqual("one", model[0][1]) + self.assertEqual([1, -0.1], model[0][0, 2]) + self.assertEqual([-0.1, 1], model[0][2, 0]) + + model[0][0, 1, 2] = (2, "two", -0.2) + self.assertEqual([2, "two", -0.2], model[0][0, 1, 2]) + + model[0][0, 1] = (3, "three") + self.assertEqual([3, "three"], model[0][0, 1]) + + model[0][1, 2] = ("four", -0.4) + self.assertEqual(["four", -0.4], model[0][1, 2]) + + model[0][0, 2] = (5, -0.5) + self.assertEqual([5, -0.5], model[0][0, 2]) + + model[0][0, 1, 2] = (6, "six", -0.6) + self.assertEqual([-0.6, 6, "six"], model[0][2, 0, 1]) + + def set_row1(): + model[0][4, 5] = ("shouldn't", "work",) + + self.assertRaises(IndexError, set_row1) + + def set_row2(): + model[0][0, 1] = (0, "zero", 0) + + self.assertRaises(ValueError, set_row2) + + def set_row3(): + model[0][0, 1] = ("shouldn't", 0) + + self.assertRaises(TypeError, set_row3) + + def set_row4(): + model[0][0, "two"] = (0, "zero") + + self.assertRaises(TypeError, set_row4) + def test_tree_model_set_value_to_none(self): # Tests allowing the usage of None to set an empty value on a model. store = Gtk.ListStore(str) @@ -1533,19 +2522,118 @@ class TestTreeModel(unittest.TestCase): filtered[0][1] = 'ONE' self.assertEqual(filtered[0][1], 'ONE') + def foo(store, iter_, data): + assert data is None + return False + + filtered.set_visible_func(foo) + filtered.refilter() + assert len(filtered) == 0 + def test_list_store_performance(self): model = Gtk.ListStore(int, str) iterations = 2000 - start = time.clock() + start = timeit.default_timer() i = iterations while i > 0: model.append([1, 'hello']) i -= 1 - end = time.clock() + end = timeit.default_timer() sys.stderr.write('[%.0f µs/append] ' % ((end - start) * 1000000 / iterations)) - + def test_filter_new_default(self): + # Test filter_new accepts implicit default of None + model = Gtk.ListStore(int) + filt = model.filter_new() + self.assertTrue(filt is not None) + + def test_tree_store_set(self): + tree_store = Gtk.TreeStore(int, int) + iter_ = tree_store.append(None) + tree_store.set(iter_) + assert tree_store.get_value(iter_, 0) == 0 + tree_store.set(iter_, 0, 42) + assert tree_store.get_value(iter_, 0) == 42 + assert tree_store.get_value(iter_, 1) == 0 + tree_store.set(iter_, 0, 42, 1, 24) + assert tree_store.get_value(iter_, 1) == 24 + tree_store.set(iter_, 1, 124) + assert tree_store.get_value(iter_, 1) == 124 + + with pytest.raises(TypeError): + tree_store.set(iter_, 0, 42, "foo", 24) + with pytest.raises(TypeError): + tree_store.set(iter_, "foo") + with pytest.raises(TypeError): + tree_store.set(iter_, [1, 2, 3]) + with pytest.raises(TypeError): + tree_store.set(iter_, 0, 42, 24) + + def test_list_store_set(self): + list_store = Gtk.ListStore(int, int) + iter_ = list_store.append() + list_store.set(iter_) + assert list_store.get_value(iter_, 0) == 0 + list_store.set(iter_, 0, 42) + assert list_store.get_value(iter_, 0) == 42 + assert list_store.get_value(iter_, 1) == 0 + list_store.set(iter_, 0, 42, 1, 24) + assert list_store.get_value(iter_, 1) == 24 + list_store.set(iter_, 1, 124) + assert list_store.get_value(iter_, 1) == 124 + + with pytest.raises(TypeError): + list_store.set(iter_, 0, 42, "foo", 24) + with pytest.raises(TypeError): + list_store.set(iter_, "foo") + with pytest.raises(TypeError): + list_store.set(iter_, [1, 2, 3]) + with pytest.raises(TypeError): + list_store.set(iter_, 0, 42, 24) + + def test_set_default_sort_func(self): + list_store = Gtk.ListStore(int) + list_store.append([2]) + list_store.append([1]) + + def sort_func(store, iter1, iter2, data): + assert data is None + cmp = lambda a, b: (a > b) - (a < b) + return cmp(store[iter1][0], store[iter2][0]) + + list_store.set_default_sort_func(sort_func) + list_store.set_sort_column_id( + Gtk.TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, Gtk.SortType.ASCENDING) + assert [v[0] for v in list_store] == [1, 2] + + def test_model_rows_reordered(self): + list_store = Gtk.ListStore(int) + list_store.append([2]) + list_store.append([1]) + list_store.rows_reordered(Gtk.TreePath.new_first(), None, [0, 1]) + list_store.rows_reordered(0, None, [0, 1]) + + def test_model_set_row(self): + list_store = Gtk.ListStore(int, int) + list_store.append([1, 2]) + iter_ = list_store.get_iter_first() + list_store.set_row(iter_, [3, 4]) + assert list_store[0][:] == [3, 4] + list_store.set_row(iter_, [None, 7]) + assert list_store[0][:] == [3, 7] + list_store.set_row(iter_, [None, GObject.Value(int, 9)]) + assert list_store[0][:] == [3, 9] + + def test_model_set_row_skip_on_none(self): + list_store = Gtk.ListStore(int, int, int, int) + list_store.append([1, 2, 3, 4]) + iter_ = list_store.get_iter_first() + list_store.set_row(iter_, [None, 7, None, 9]) + assert list_store[0][:] == [1, 7, 3, 9] + + +@unittest.skipIf(sys.platform == "darwin", "hangs") @unittest.skipUnless(Gtk, 'Gtk not available') class TestTreeView(unittest.TestCase): def test_tree_view(self): @@ -1563,10 +2651,13 @@ class TestTreeView(unittest.TestCase): def test_tree_view_column(self): cell = Gtk.CellRendererText() - Gtk.TreeViewColumn(title='This is just a test', - cell_renderer=cell, - text=0, - style=2) + col = Gtk.TreeViewColumn(title='This is just a test', + cell_renderer=cell, + text=0, + style=2) + + # Regression test for: https://bugzilla.gnome.org/show_bug.cgi?id=711173 + col.set_cell_data_func(cell, None, None) def test_tree_view_add_column_with_attributes(self): model = Gtk.ListStore(str, str, str) @@ -1575,7 +2666,7 @@ class TestTreeView(unittest.TestCase): model.append(['cell13', 'cell11', 'cell12']) model.append(['cell23', 'cell21', 'cell22']) - tree = Gtk.TreeView(model) + tree = Gtk.TreeView(model=model) cell1 = Gtk.CellRendererText() cell2 = Gtk.CellRendererText() cell3 = Gtk.CellRendererText() @@ -1589,8 +2680,13 @@ class TestTreeView(unittest.TestCase): with realized(tree): tree.set_cursor(model[0].path) - while Gtk.events_pending(): - Gtk.main_iteration() + if Gtk._version == "4.0": + context = GLib.MainContext() + while context.pending(): + context.iteration(False) + else: + while Gtk.events_pending(): + Gtk.main_iteration() self.assertEqual(tree.get_column(0).get_title(), 'Head1') self.assertEqual(tree.get_column(1).get_title(), 'Head2') @@ -1598,10 +2694,12 @@ class TestTreeView(unittest.TestCase): self.assertEqual(tree.get_column(3).get_title(), 'Head4') # cursor should be at the first row - self.assertEqual(cell1.props.text, 'cell11') - self.assertEqual(cell2.props.text, 'cell12') - self.assertEqual(cell3.props.text, 'cell13') - self.assertEqual(cell4.props.text, None) + if not GTK4: + # not sure why this doesn't work with gtk4 + self.assertEqual(cell1.props.text, 'cell11') + self.assertEqual(cell2.props.text, 'cell12') + self.assertEqual(cell3.props.text, 'cell13') + self.assertEqual(cell4.props.text, None) def test_tree_view_column_set_attributes(self): store = Gtk.ListStore(int, str) @@ -1620,7 +2718,9 @@ class TestTreeView(unittest.TestCase): column.set_attributes(cell, text=1) with realized(treeview): - self.assertTrue(cell.props.text in directors) + if not GTK4: + # not sure why this doesn't work with gtk4 + self.assertTrue(cell.props.text in directors) def test_tree_selection(self): store = Gtk.ListStore(int, str) @@ -1651,12 +2751,31 @@ class TestTreeView(unittest.TestCase): self.assertEqual(m, store) self.assertEqual(store.get_path(s), firstpath) + sel.unselect_all() + (m, s) = sel.get_selected() + assert s is None + + sel.select_path(0) + m, r = sel.get_selected_rows() + assert m == store + assert len(r) == 1 + assert r[0] == store[0].path + + def test_scroll_to_cell(self): + store = Gtk.ListStore(int, str) + store.append((0, "foo")) + view = Gtk.TreeView() + view.set_model(store) + view.scroll_to_cell(store[0].path) + view.scroll_to_cell(0) + @unittest.skipUnless(Gtk, 'Gtk not available') class TestTextBuffer(unittest.TestCase): def test_text_buffer(self): self.assertEqual(Gtk.TextBuffer, gi.overrides.Gtk.TextBuffer) buffer = Gtk.TextBuffer() + assert buffer.get_tag_table() is not None tag = buffer.create_tag('title', font='Sans 18') self.assertEqual(tag.props.name, 'title') @@ -1697,21 +2816,52 @@ class TestTextBuffer(unittest.TestCase): self.assertTrue(sel[1].equal(end)) buffer.set_text('') + buffer.insert_with_tags(buffer.get_start_iter(), 'HelloHello') + start, end = buffer.get_bounds() + text = buffer.get_text(start, end, False) + self.assertEqual(text, 'HelloHello') + + buffer.set_text('') + buffer.insert_with_tags_by_name(buffer.get_start_iter(), 'HelloHello') + start, end = buffer.get_bounds() + text = buffer.get_text(start, end, False) + self.assertEqual(text, 'HelloHello') + + try: + starts_tag = Gtk.TextIter.starts_tag + except AttributeError: + starts_tag = Gtk.TextIter.begins_tag + + buffer.set_text('') buffer.insert_with_tags(buffer.get_start_iter(), 'HelloHello', tag) (start, end) = buffer.get_bounds() - self.assertTrue(start.begins_tag(tag)) + self.assertTrue(starts_tag(start, tag)) self.assertTrue(start.has_tag(tag)) buffer.set_text('') buffer.insert_with_tags_by_name(buffer.get_start_iter(), 'HelloHello', 'title') (start, end) = buffer.get_bounds() - self.assertTrue(start.begins_tag(tag)) + self.assertTrue(starts_tag(start, tag)) self.assertTrue(start.has_tag(tag)) self.assertRaises(ValueError, buffer.insert_with_tags_by_name, buffer.get_start_iter(), 'HelloHello', 'unknowntag') + def test_insert(self): + buffer = Gtk.TextBuffer() + start = buffer.get_bounds()[0] + with pytest.raises(TypeError): + buffer.insert(start, 42) + + with pytest.raises(TypeError): + buffer.insert_at_cursor(42) + def test_text_iter(self): + try: + starts_tag = Gtk.TextIter.starts_tag + except AttributeError: + starts_tag = Gtk.TextIter.begins_tag + self.assertEqual(Gtk.TextIter, gi.overrides.Gtk.TextIter) buffer = Gtk.TextBuffer() buffer.set_text('Hello Jane Hello Bob') @@ -1719,12 +2869,12 @@ class TestTextBuffer(unittest.TestCase): (start, end) = buffer.get_bounds() start.forward_chars(10) buffer.apply_tag(tag, start, end) - self.assertTrue(start.begins_tag()) + self.assertTrue(starts_tag(start)) self.assertTrue(end.ends_tag()) self.assertTrue(start.toggles_tag()) self.assertTrue(end.toggles_tag()) start.backward_chars(1) - self.assertFalse(start.begins_tag()) + self.assertFalse(starts_tag(start)) self.assertFalse(start.ends_tag()) self.assertFalse(start.toggles_tag()) @@ -1746,3 +2896,146 @@ class TestTextBuffer(unittest.TestCase): None) self.assertEqual(start.get_offset(), 6) self.assertEqual(end.get_offset(), 11) + + def test_insert_text_signal_location_modification(self): + # Regression test for: https://bugzilla.gnome.org/show_bug.cgi?id=736175 + + def callback(buffer, location, text, length): + location.assign(buffer.get_end_iter()) + + buffer = Gtk.TextBuffer() + buffer.set_text('first line\n') + buffer.connect('insert-text', callback) + + # attempt insertion at the beginning of the buffer, the callback will + # modify the insert location to the end. + buffer.place_cursor(buffer.get_start_iter()) + buffer.insert_at_cursor('second line\n') + + self.assertEqual(buffer.get_property('text'), + 'first line\nsecond line\n') + + @unittest.skipIf(gtkver() < (3, 20, 0), "broken with older gtk") + def test_backward_find_char(self): + buffer = Gtk.TextBuffer() + buffer.set_text('abc') + res = buffer.get_iter_at_line(99) + if GTK4: + end = res.iter + else: + end = res + + values = [] + + def pred_func(ch, user_data): + values.append(ch) + return ch == u"a" + + self.assertTrue(end.backward_find_char(pred_func)) + self.assertEqual(values, [u"c", u"b", u"a"]) + + +@unittest.skipUnless(Gtk, 'Gtk not available') +@unittest.skipIf(Gtk_version == "4.0", "not in gtk4") +class TestPaned(unittest.TestCase): + + def test_pack_defaults(self): + p = Gtk.Paned() + l1 = Gtk.Label() + l2 = Gtk.Label() + p.pack1(l1) + p.pack2(l2) + assert p.get_children() == [l1, l2] + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestContainer(unittest.TestCase): + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_child_set_property(self): + box = Gtk.Box() + child = Gtk.Button() + box.pack_start(child, expand=False, fill=True, padding=0) + + box.child_set_property(child, 'padding', 42) + + value = GObject.Value(int) + box.child_get_property(child, 'padding', value) + self.assertEqual(value.get_int(), 42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_child_get_property_gvalue(self): + box = Gtk.Box() + child = Gtk.Button() + box.pack_start(child, expand=False, fill=True, padding=42) + + value = GObject.Value(int) + box.child_get_property(child, 'padding', value) + self.assertEqual(value.get_int(), 42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_child_get_property_return_with_explicit_gvalue(self): + box = Gtk.Box() + child = Gtk.Button() + box.pack_start(child, expand=False, fill=True, padding=42) + + value = GObject.Value(int) + result = box.child_get_property(child, 'padding', value) + self.assertEqual(result, 42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_child_get_property_return_with_implicit_gvalue(self): + box = Gtk.Box() + child = Gtk.Button() + box.pack_start(child, expand=False, fill=True, padding=42) + + result = box.child_get_property(child, 'padding') + self.assertEqual(result, 42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_child_get_property_error(self): + box = Gtk.Box() + child = Gtk.Button() + if Gtk_version == "4.0": + box.add(child) + else: + box.pack_start(child, expand=False, fill=True, padding=42) + with self.assertRaises(ValueError): + box.child_get_property(child, 'not-a-valid-child-property') + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_child_get_and_set(self): + box = Gtk.Box() + child = Gtk.Button() + box.pack_start(child, expand=True, fill=True, padding=42) + + expand, fill, padding = box.child_get(child, 'expand', 'fill', 'padding') + self.assertEqual(expand, True) + self.assertEqual(fill, True) + self.assertEqual(padding, 42) + + box.child_set(child, expand=False, fill=False, padding=21, pack_type=1) + expand, fill, padding, pack_type = box.child_get(child, 'expand', 'fill', 'padding', 'pack-type') + self.assertEqual(expand, False) + self.assertEqual(fill, False) + self.assertEqual(padding, 21) + + +def test_button_focus_on_click(): + b = Gtk.Button() + b.set_focus_on_click(True) + assert b.get_focus_on_click() + b.set_focus_on_click(False) + assert not b.get_focus_on_click() + + +@pytest.mark.parametrize( + "data", + [ + "* { background: white; }", + "* { background: white; }".encode() + ] +) +def test_css_provider_load_from_data(data): + provider = Gtk.CssProvider.new() + provider.load_from_data(data) diff --git a/tests/test_overrides_pango.py b/tests/test_overrides_pango.py index 42d4de9..2ec4cb1 100644 --- a/tests/test_overrides_pango.py +++ b/tests/test_overrides_pango.py @@ -5,9 +5,10 @@ import unittest try: from gi.repository import Pango - Pango + from gi.repository import PangoCairo except ImportError: Pango = None + PangoCairo = None @unittest.skipUnless(Pango, 'Pango not available') @@ -31,7 +32,36 @@ class TestPango(unittest.TestCase): layout.set_markup("Foobar") self.assertEqual(layout.get_text(), "Foobar") + def test_layout_set_markup(self): + context = Pango.Context() + layout = Pango.Layout(context) + layout.set_markup("abc") + assert layout.get_text() == "abc" + layout.set_markup("abc", -1) + assert layout.get_text() == "abc" + layout.set_markup("abc", 2) + assert layout.get_text() == "ab" + + def test_layout_set_test(self): + context = Pango.Context() + layout = Pango.Layout(context) + layout.set_text("abc") + assert layout.get_text() == "abc" + layout.set_text("abc", -1) + assert layout.get_text() == "abc" + layout.set_text("abc", 2) + assert layout.get_text() == "ab" + def test_break_keyword_escape(self): # https://bugzilla.gnome.org/show_bug.cgi?id=697363 self.assertTrue(hasattr(Pango, 'break_')) self.assertTrue(Pango.break_ is not None) + + def test_context_get_metrics(self): + # Test default "language" argument + font_map = PangoCairo.font_map_get_default() + context = font_map.create_context() + desc = Pango.FontDescription('monospace') + metrics1 = context.get_metrics(desc) + metrics2 = context.get_metrics(desc, context.get_language()) + self.assertEqual(metrics1.get_ascent(), metrics2.get_ascent()) diff --git a/tests/test_properties.py b/tests/test_properties.py index ef6b867..f8b7823 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,13 +1,15 @@ -# coding=utf-8 - +import os +import gc import sys import struct import types import unittest +import tempfile + +import pytest from gi.repository import GObject -from gi.repository.GObject import GType, new, PARAM_READWRITE, \ - PARAM_CONSTRUCT, PARAM_READABLE, PARAM_WRITABLE, PARAM_CONSTRUCT_ONLY +from gi.repository.GObject import ParamFlags, GType, new from gi.repository.GObject import \ TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG, TYPE_INT64, \ TYPE_UINT64, TYPE_GTYPE, TYPE_INVALID, TYPE_NONE, TYPE_STRV, \ @@ -15,62 +17,66 @@ from gi.repository.GObject import \ TYPE_DOUBLE, TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \ TYPE_STRING, TYPE_PYOBJECT, TYPE_VARIANT -from gi.repository.GObject import \ - G_MININT, G_MAXINT, G_MAXUINT, G_MINLONG, G_MAXLONG, G_MAXULONG, \ - G_MAXUINT64, G_MAXINT64, G_MININT64 +from gi.repository.GLib import \ + MININT, MAXINT, MAXUINT, MINLONG, MAXLONG, MAXULONG, \ + MAXUINT64, MAXINT64, MININT64 from gi.repository import Gio from gi.repository import GLib -from gi.repository import Regress from gi.repository import GIMarshallingTests -from gi._gobject import propertyhelper - -if sys.version_info < (3, 0): - TEST_UTF8 = "\xe2\x99\xa5" - UNICODE_UTF8 = unicode(TEST_UTF8, 'UTF-8') -else: - TEST_UTF8 = "♥" - UNICODE_UTF8 = TEST_UTF8 +from gi.repository import Regress +from gi import _propertyhelper as propertyhelper -from compathelper import _long +from .helper import capture_glib_warnings class PropertyObject(GObject.GObject): normal = GObject.Property(type=str) construct = GObject.Property( type=str, - flags=PARAM_READWRITE | PARAM_CONSTRUCT, default='default') + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT, + default='default') + construct_only = GObject.Property( type=str, - flags=PARAM_READWRITE | PARAM_CONSTRUCT_ONLY) + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT_ONLY) + uint64 = GObject.Property( - type=TYPE_UINT64, flags=PARAM_READWRITE | PARAM_CONSTRUCT) + type=TYPE_UINT64, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT) enum = GObject.Property( type=Gio.SocketType, default=Gio.SocketType.STREAM) boxed = GObject.Property( - type=GLib.Regex, flags=PARAM_READWRITE | PARAM_CONSTRUCT) + type=GLib.Regex, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT) flags = GObject.Property( - type=GIMarshallingTests.Flags, flags=PARAM_READWRITE | PARAM_CONSTRUCT, + type=GIMarshallingTests.Flags, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT, default=GIMarshallingTests.Flags.VALUE1) gtype = GObject.Property( - type=TYPE_GTYPE, flags=PARAM_READWRITE | PARAM_CONSTRUCT) + type=TYPE_GTYPE, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT) strings = GObject.Property( - type=TYPE_STRV, flags=PARAM_READWRITE | PARAM_CONSTRUCT) + type=TYPE_STRV, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT) variant = GObject.Property( - type=TYPE_VARIANT, flags=PARAM_READWRITE | PARAM_CONSTRUCT) + type=TYPE_VARIANT, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT) variant_def = GObject.Property( - type=TYPE_VARIANT, flags=PARAM_READWRITE | PARAM_CONSTRUCT, + type=TYPE_VARIANT, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT, default=GLib.Variant('i', 42)) interface = GObject.Property( - type=Gio.File, flags=PARAM_READWRITE | PARAM_CONSTRUCT) + type=Gio.File, + flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT) class PropertyInheritanceObject(Regress.TestObj): @@ -160,12 +166,19 @@ class TestPropertyObject(unittest.TestCase): self.assertEqual(obj.props.construct, "789") def test_utf8(self): - obj = new(PropertyObject, construct_only=UNICODE_UTF8) - self.assertEqual(obj.props.construct_only, TEST_UTF8) - obj.set_property('construct', UNICODE_UTF8) - self.assertEqual(obj.props.construct, TEST_UTF8) - obj.props.normal = UNICODE_UTF8 - self.assertEqual(obj.props.normal, TEST_UTF8) + test_utf8 = "♥" + unicode_utf8 = u"♥" + obj = new(PropertyObject, construct_only=unicode_utf8) + self.assertEqual(obj.props.construct_only, test_utf8) + obj.set_property('construct', unicode_utf8) + self.assertEqual(obj.props.construct, test_utf8) + obj.props.normal = unicode_utf8 + self.assertEqual(obj.props.normal, test_utf8) + + def test_utf8_lone_surrogate(self): + obj = PropertyObject() + with pytest.raises(TypeError): + obj.set_property('construct', '\ud83d') def test_int_to_str(self): obj = new(PropertyObject, construct_only=1) @@ -186,12 +199,12 @@ class TestPropertyObject(unittest.TestCase): def test_uint64(self): obj = new(PropertyObject) self.assertEqual(obj.props.uint64, 0) - obj.props.uint64 = _long(1) - self.assertEqual(obj.props.uint64, _long(1)) obj.props.uint64 = 1 - self.assertEqual(obj.props.uint64, _long(1)) + self.assertEqual(obj.props.uint64, 1) + obj.props.uint64 = 1 + self.assertEqual(obj.props.uint64, 1) - self.assertRaises((TypeError, OverflowError), obj.set_property, "uint64", _long(-1)) + self.assertRaises((TypeError, OverflowError), obj.set_property, "uint64", -1) self.assertRaises((TypeError, OverflowError), obj.set_property, "uint64", -1) def test_uint64_default_value(self): @@ -199,8 +212,8 @@ class TestPropertyObject(unittest.TestCase): class TimeControl(GObject.GObject): __gproperties__ = { 'time': (TYPE_UINT64, 'Time', 'Time', - _long(0), (1 << 64) - 1, _long(0), - PARAM_READABLE) + 0, (1 << 64) - 1, 0, + ParamFlags.READABLE) } except OverflowError: (etype, ex) = sys.exc_info()[2:] @@ -234,6 +247,10 @@ class TestPropertyObject(unittest.TestCase): self.assertRaises(TypeError, GObject.Property, type=Gio.SocketType, default=1) + def test_repr(self): + prop = GObject.Property(type=int) + assert repr(prop) == "<GObject Property (uninitialized) (gint)>" + def test_flags(self): obj = new(PropertyObject) self.assertEqual(obj.props.flags, GIMarshallingTests.Flags.VALUE1) @@ -396,10 +413,11 @@ class TestPropertyObject(unittest.TestCase): def test_interface(self): obj = new(PropertyObject) - file = Gio.File.new_for_path('/some/path') + path = os.path.join(tempfile.gettempdir(), "some", "path") + file = Gio.File.new_for_path(path) obj.props.interface = file - self.assertEqual(obj.props.interface.get_path(), '/some/path') - self.assertEqual(obj.interface.get_path(), '/some/path') + self.assertEqual(obj.props.interface.get_path(), path) + self.assertEqual(obj.interface.get_path(), path) self.assertRaises(TypeError, setattr, obj, 'interface', 'foo') self.assertRaises(TypeError, setattr, obj, 'interface', object()) @@ -433,7 +451,7 @@ class TestPropertyObject(unittest.TestCase): d = {} for key, (gtype, min, max) in types_.items(): d[key] = (gtype, 'blurb', 'desc', min, max, 0, - PARAM_READABLE | PARAM_WRITABLE) + ParamFlags.READABLE | ParamFlags.WRITABLE) return d class RangeCheck(GObject.GObject): @@ -486,9 +504,9 @@ class TestProperty(unittest.TestCase): def test_simple(self): class C(GObject.GObject): str = GObject.Property(type=str) - int = GObject.Property(type=int) float = GObject.Property(type=float) - long = GObject.Property(type=_long) + long = GObject.Property(type=int) + int = GObject.Property(type=int) self.assertTrue(hasattr(C.props, 'str')) self.assertTrue(hasattr(C.props, 'int')) @@ -508,9 +526,9 @@ class TestProperty(unittest.TestCase): o.float = 3.14 self.assertEqual(o.float, 3.14) - self.assertEqual(o.long, _long(0)) - o.long = _long(100) - self.assertEqual(o.long, _long(100)) + self.assertEqual(o.long, 0) + o.long = 100 + self.assertEqual(o.long, 100) def test_custom_getter(self): class C(GObject.GObject): @@ -522,6 +540,23 @@ class TestProperty(unittest.TestCase): self.assertEqual(o.prop, 'value') self.assertRaises(TypeError, setattr, o, 'prop', 'xxx') + def test_getter_exception(self): + class C(GObject.Object): + @GObject.Property(type=int) + def prop(self): + raise ValueError('something bad happend') + + o = C() + + with self.assertRaisesRegex(ValueError, 'something bad happend'): + o.prop + + with self.assertRaisesRegex(ValueError, 'something bad happend'): + o.get_property('prop') + + with self.assertRaisesRegex(ValueError, 'something bad happend'): + o.props.prop + def test_custom_setter(self): class C(GObject.GObject): def set_prop(self, value): @@ -618,12 +653,12 @@ class TestProperty(unittest.TestCase): def test_range(self): types_ = [ - (TYPE_INT, G_MININT, G_MAXINT), - (TYPE_UINT, 0, G_MAXUINT), - (TYPE_LONG, G_MINLONG, G_MAXLONG), - (TYPE_ULONG, 0, G_MAXULONG), - (TYPE_INT64, G_MININT64, G_MAXINT64), - (TYPE_UINT64, 0, G_MAXUINT64), + (TYPE_INT, MININT, MAXINT), + (TYPE_UINT, 0, MAXUINT), + (TYPE_LONG, MINLONG, MAXLONG), + (TYPE_ULONG, 0, MAXULONG), + (TYPE_INT64, MININT64, MAXINT64), + (TYPE_UINT64, 0, MAXUINT64), ] for gtype, min, max in types_: @@ -653,8 +688,7 @@ class TestProperty(unittest.TestCase): # we test known-bad values here which cause Gtk-WARNING logs. # Explicitly allow these for this test. - old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_CRITICAL) - try: + with capture_glib_warnings(allow_warnings=True, allow_criticals=True): o = C() self.assertEqual(o.prop_int, 1) @@ -677,8 +711,6 @@ class TestProperty(unittest.TestCase): o.prop_float = 10.51 self.assertEqual(o.prop_float, 7.75) - finally: - GLib.log_set_always_fatal(old_mask) def test_multiple_instances(self): class C(GObject.GObject): @@ -791,7 +823,7 @@ class TestProperty(unittest.TestCase): GObject.Property(type=GObject.TYPE_DOUBLE, minimum=-1) # Bug 644039 - + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") def test_reference_count(self): # We can check directly if an object gets finalized, so we will # observe it indirectly through the refcount of a member object. @@ -831,8 +863,6 @@ class TestProperty(unittest.TestCase): def test_python_to_glib_type_mapping(self): tester = GObject.Property() self.assertEqual(tester._type_from_python(int), GObject.TYPE_INT) - if sys.version_info < (3, 0): - self.assertEqual(tester._type_from_python(long), GObject.TYPE_LONG) self.assertEqual(tester._type_from_python(bool), GObject.TYPE_BOOLEAN) self.assertEqual(tester._type_from_python(float), GObject.TYPE_DOUBLE) self.assertEqual(tester._type_from_python(str), GObject.TYPE_STRING) @@ -918,5 +948,415 @@ class TestInstallProperties(unittest.TestCase): self.assertRaises(ValueError, propertyhelper.install_properties, self.ClassWithPropertyRedefined) -if __name__ == '__main__': - unittest.main() + +class CPropertiesTestBase(object): + # Tests for properties implemented in C not Python. + + def setUp(self): + self.obj = GIMarshallingTests.PropertiesObject() + + def get_prop(self, obj, name): + raise NotImplementedError + + def set_prop(self, obj, name, value): + raise NotImplementedError + + # https://bugzilla.gnome.org/show_bug.cgi?id=780652 + @unittest.skipUnless( + "some_flags" in dir(GIMarshallingTests.PropertiesObject.props), + "tool old gi") + def test_flags(self): + self.assertEqual( + self.get_prop(self.obj, 'some-flags'), + GIMarshallingTests.Flags.VALUE1) + self.set_prop(self.obj, 'some-flags', GIMarshallingTests.Flags.VALUE2) + self.assertEqual(self.get_prop(self.obj, 'some-flags'), + GIMarshallingTests.Flags.VALUE2) + + obj = GIMarshallingTests.PropertiesObject( + some_flags=GIMarshallingTests.Flags.VALUE3) + self.assertEqual(self.get_prop(obj, 'some-flags'), + GIMarshallingTests.Flags.VALUE3) + + # https://bugzilla.gnome.org/show_bug.cgi?id=780652 + @unittest.skipUnless( + "some_enum" in dir(GIMarshallingTests.PropertiesObject.props), + "tool old gi") + def test_enum(self): + self.assertEqual( + self.get_prop(self.obj, 'some-enum'), + GIMarshallingTests.GEnum.VALUE1) + self.set_prop(self.obj, 'some-enum', GIMarshallingTests.GEnum.VALUE2) + self.assertEqual(self.get_prop(self.obj, 'some-enum'), + GIMarshallingTests.GEnum.VALUE2) + + obj = GIMarshallingTests.PropertiesObject( + some_enum=GIMarshallingTests.GEnum.VALUE3) + self.assertEqual(self.get_prop(obj, 'some-enum'), + GIMarshallingTests.GEnum.VALUE3) + + def test_boolean(self): + self.assertEqual(self.get_prop(self.obj, 'some-boolean'), False) + self.set_prop(self.obj, 'some-boolean', True) + self.assertEqual(self.get_prop(self.obj, 'some-boolean'), True) + + obj = GIMarshallingTests.PropertiesObject(some_boolean=True) + self.assertEqual(self.get_prop(obj, 'some-boolean'), True) + + def test_char(self): + self.assertEqual(self.get_prop(self.obj, 'some-char'), 0) + self.set_prop(self.obj, 'some-char', GLib.MAXINT8) + self.assertEqual(self.get_prop(self.obj, 'some-char'), GLib.MAXINT8) + + obj = GIMarshallingTests.PropertiesObject(some_char=-42) + self.assertEqual(self.get_prop(obj, 'some-char'), -42) + + with pytest.raises(OverflowError): + self.set_prop(obj, 'some-char', GLib.MAXINT8 + 1) + with pytest.raises(OverflowError): + self.set_prop(obj, 'some-char', GLib.MININT8 - 1) + + self.set_prop(obj, 'some-char', b"\x44") + assert self.get_prop(obj, 'some-char') == 0x44 + + self.set_prop(obj, 'some-char', b"\xff") + assert self.get_prop(obj, 'some-char') == -1 + + obj = GIMarshallingTests.PropertiesObject(some_char=u"\x7f") + assert self.get_prop(obj, 'some-char') == 0x7f + + with pytest.raises(TypeError): + GIMarshallingTests.PropertiesObject(some_char=u"€") + + with pytest.raises(TypeError): + GIMarshallingTests.PropertiesObject(some_char=u"\ud83d") + + def test_uchar(self): + self.assertEqual(self.get_prop(self.obj, 'some-uchar'), 0) + self.set_prop(self.obj, 'some-uchar', GLib.MAXUINT8) + self.assertEqual(self.get_prop(self.obj, 'some-uchar'), GLib.MAXUINT8) + + obj = GIMarshallingTests.PropertiesObject(some_uchar=42) + self.assertEqual(self.get_prop(obj, 'some-uchar'), 42) + + with pytest.raises(OverflowError): + self.set_prop(obj, 'some-uchar', GLib.MAXUINT8 + 1) + with pytest.raises(OverflowError): + self.set_prop(obj, 'some-uchar', -1) + + self.set_prop(obj, 'some-uchar', b"\x57") + assert self.get_prop(obj, 'some-uchar') == 0x57 + + self.set_prop(obj, 'some-uchar', b"\xff") + assert self.get_prop(obj, 'some-uchar') == 255 + + obj = GIMarshallingTests.PropertiesObject(some_uchar=u"\x7f") + assert self.get_prop(obj, 'some-uchar') == 127 + + with pytest.raises(TypeError): + GIMarshallingTests.PropertiesObject(some_uchar=u"\x80") + + with pytest.raises(TypeError): + GIMarshallingTests.PropertiesObject(some_uchar=u"\ud83d") + + def test_int(self): + self.assertEqual(self.get_prop(self.obj, 'some_int'), 0) + self.set_prop(self.obj, 'some-int', GLib.MAXINT) + self.assertEqual(self.get_prop(self.obj, 'some_int'), GLib.MAXINT) + + obj = GIMarshallingTests.PropertiesObject(some_int=-42) + self.assertEqual(self.get_prop(obj, 'some-int'), -42) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-int', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-int', None) + + self.assertEqual(self.get_prop(obj, 'some-int'), -42) + + def test_uint(self): + self.assertEqual(self.get_prop(self.obj, 'some_uint'), 0) + self.set_prop(self.obj, 'some-uint', GLib.MAXUINT) + self.assertEqual(self.get_prop(self.obj, 'some_uint'), GLib.MAXUINT) + + obj = GIMarshallingTests.PropertiesObject(some_uint=42) + self.assertEqual(self.get_prop(obj, 'some-uint'), 42) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-uint', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-uint', None) + + self.assertEqual(self.get_prop(obj, 'some-uint'), 42) + + def test_long(self): + self.assertEqual(self.get_prop(self.obj, 'some_long'), 0) + self.set_prop(self.obj, 'some-long', GLib.MAXLONG) + self.assertEqual(self.get_prop(self.obj, 'some_long'), GLib.MAXLONG) + + obj = GIMarshallingTests.PropertiesObject(some_long=-42) + self.assertEqual(self.get_prop(obj, 'some-long'), -42) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-long', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-long', None) + + self.assertEqual(self.get_prop(obj, 'some-long'), -42) + + def test_ulong(self): + self.assertEqual(self.get_prop(self.obj, 'some_ulong'), 0) + self.set_prop(self.obj, 'some-ulong', GLib.MAXULONG) + self.assertEqual(self.get_prop(self.obj, 'some_ulong'), GLib.MAXULONG) + + obj = GIMarshallingTests.PropertiesObject(some_ulong=42) + self.assertEqual(self.get_prop(obj, 'some-ulong'), 42) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-ulong', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-ulong', None) + + self.assertEqual(self.get_prop(obj, 'some-ulong'), 42) + + def test_int64(self): + self.assertEqual(self.get_prop(self.obj, 'some-int64'), 0) + self.set_prop(self.obj, 'some-int64', GLib.MAXINT64) + self.assertEqual(self.get_prop(self.obj, 'some-int64'), GLib.MAXINT64) + + obj = GIMarshallingTests.PropertiesObject(some_int64=-4200000000000000) + self.assertEqual(self.get_prop(obj, 'some-int64'), -4200000000000000) + + def test_uint64(self): + self.assertEqual(self.get_prop(self.obj, 'some-uint64'), 0) + self.set_prop(self.obj, 'some-uint64', GLib.MAXUINT64) + self.assertEqual(self.get_prop(self.obj, 'some-uint64'), GLib.MAXUINT64) + + obj = GIMarshallingTests.PropertiesObject(some_uint64=4200000000000000) + self.assertEqual(self.get_prop(obj, 'some-uint64'), 4200000000000000) + + def test_float(self): + self.assertEqual(self.get_prop(self.obj, 'some-float'), 0) + self.set_prop(self.obj, 'some-float', GLib.MAXFLOAT) + self.assertEqual(self.get_prop(self.obj, 'some-float'), GLib.MAXFLOAT) + + obj = GIMarshallingTests.PropertiesObject(some_float=42.42) + self.assertAlmostEqual(self.get_prop(obj, 'some-float'), 42.42, places=4) + + obj = GIMarshallingTests.PropertiesObject(some_float=42) + self.assertAlmostEqual(self.get_prop(obj, 'some-float'), 42.0, places=4) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-float', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-float', None) + + self.assertAlmostEqual(self.get_prop(obj, 'some-float'), 42.0, places=4) + + def test_double(self): + self.assertEqual(self.get_prop(self.obj, 'some-double'), 0) + self.set_prop(self.obj, 'some-double', GLib.MAXDOUBLE) + self.assertEqual(self.get_prop(self.obj, 'some-double'), GLib.MAXDOUBLE) + + obj = GIMarshallingTests.PropertiesObject(some_double=42.42) + self.assertAlmostEqual(self.get_prop(obj, 'some-double'), 42.42) + + obj = GIMarshallingTests.PropertiesObject(some_double=42) + self.assertAlmostEqual(self.get_prop(obj, 'some-double'), 42.0) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-double', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-double', None) + + self.assertAlmostEqual(self.get_prop(obj, 'some-double'), 42.0) + + def test_strv(self): + self.assertEqual(self.get_prop(self.obj, 'some-strv'), []) + self.set_prop(self.obj, 'some-strv', ['hello', 'world']) + self.assertEqual(self.get_prop(self.obj, 'some-strv'), ['hello', 'world']) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', 1) + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', [1, 2]) + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', ['foo', 1]) + + self.assertEqual(self.get_prop(self.obj, 'some-strv'), ['hello', 'world']) + + obj = GIMarshallingTests.PropertiesObject(some_strv=['hello', 'world']) + self.assertEqual(self.get_prop(obj, 'some-strv'), ['hello', 'world']) + + # unicode on py2 + obj = GIMarshallingTests.PropertiesObject(some_strv=[u'foo']) + self.assertEqual(self.get_prop(obj, 'some-strv'), [u'foo']) + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', + [u'foo', 1]) + + def test_boxed_struct(self): + self.assertEqual(self.get_prop(self.obj, 'some-boxed-struct'), None) + + class GStrv(list): + __gtype__ = GObject.TYPE_STRV + + struct1 = GIMarshallingTests.BoxedStruct() + struct1.long_ = 1 + + self.set_prop(self.obj, 'some-boxed-struct', struct1) + self.assertEqual(self.get_prop(self.obj, 'some-boxed-struct').long_, 1) + self.assertEqual(self.obj.some_boxed_struct.long_, 1) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-struct', 1) + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-struct', 'foo') + + obj = GIMarshallingTests.PropertiesObject(some_boxed_struct=struct1) + self.assertEqual(self.get_prop(obj, 'some-boxed-struct').long_, 1) + + def test_boxed_glist(self): + self.assertEqual(self.get_prop(self.obj, 'some-boxed-glist'), []) + + list_ = [GLib.MININT, 42, GLib.MAXINT] + self.set_prop(self.obj, 'some-boxed-glist', list_) + self.assertEqual(self.get_prop(self.obj, 'some-boxed-glist'), list_) + self.set_prop(self.obj, 'some-boxed-glist', []) + self.assertEqual(self.get_prop(self.obj, 'some-boxed-glist'), []) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-glist', 1) + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-glist', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-glist', ['a']) + + def test_annotated_glist(self): + obj = Regress.TestObj() + self.assertEqual(self.get_prop(obj, 'list'), []) + + self.set_prop(obj, 'list', ['1', '2', '3']) + self.assertTrue(isinstance(self.get_prop(obj, 'list'), list)) + self.assertEqual(self.get_prop(obj, 'list'), ['1', '2', '3']) + + @unittest.expectedFailure + def test_boxed_glist_ctor(self): + list_ = [GLib.MININT, 42, GLib.MAXINT] + obj = GIMarshallingTests.PropertiesObject(some_boxed_glist=list_) + self.assertEqual(self.get_prop(obj, 'some-boxed-glist'), list_) + + def test_variant(self): + self.assertEqual(self.get_prop(self.obj, 'some-variant'), None) + + self.set_prop(self.obj, 'some-variant', GLib.Variant('o', '/myobj')) + self.assertEqual(self.get_prop(self.obj, 'some-variant').get_type_string(), 'o') + self.assertEqual(self.get_prop(self.obj, 'some-variant').print_(False), "'/myobj'") + + self.set_prop(self.obj, 'some-variant', None) + self.assertEqual(self.get_prop(self.obj, 'some-variant'), None) + + obj = GIMarshallingTests.PropertiesObject(some_variant=GLib.Variant('b', True)) + self.assertEqual(self.get_prop(obj, 'some-variant').get_type_string(), 'b') + self.assertEqual(self.get_prop(obj, 'some-variant').get_boolean(), True) + + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-variant', 'foo') + self.assertRaises(TypeError, self.set_prop, self.obj, 'some-variant', 23) + + self.assertEqual(self.get_prop(obj, 'some-variant').get_type_string(), 'b') + self.assertEqual(self.get_prop(obj, 'some-variant').get_boolean(), True) + + def test_setting_several_properties(self): + obj = GIMarshallingTests.PropertiesObject() + obj.set_properties(some_uchar=54, some_int=42) + self.assertEqual(42, self.get_prop(obj, 'some-int')) + self.assertEqual(54, self.get_prop(obj, 'some-uchar')) + + def test_gtype(self): + obj = Regress.TestObj() + self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_INVALID) + self.set_prop(obj, 'gtype', int) + self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_INT) + + obj = Regress.TestObj(gtype=int) + self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_INT) + self.set_prop(obj, 'gtype', str) + self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_STRING) + + def test_hash_table(self): + obj = Regress.TestObj() + self.assertEqual(self.get_prop(obj, 'hash-table'), None) + + self.set_prop(obj, 'hash-table', {'mec': 56}) + self.assertTrue(isinstance(self.get_prop(obj, 'hash-table'), dict)) + self.assertEqual(list(self.get_prop(obj, 'hash-table').items())[0], + ('mec', 56)) + + def test_parent_class(self): + class A(Regress.TestObj): + prop1 = GObject.Property(type=int) + + a = A() + self.set_prop(a, 'int', 20) + self.assertEqual(self.get_prop(a, 'int'), 20) + + # test parent property which needs introspection + self.set_prop(a, 'list', ("str1", "str2")) + self.assertEqual(self.get_prop(a, 'list'), ["str1", "str2"]) + + def test_held_object_ref_count_getter(self): + holder = GIMarshallingTests.PropertiesObject() + held = GObject.Object() + + self.assertEqual(holder.__grefcount__, 1) + self.assertEqual(held.__grefcount__, 1) + + self.set_prop(holder, 'some-object', held) + self.assertEqual(holder.__grefcount__, 1) + + initial_ref_count = held.__grefcount__ + self.get_prop(holder, 'some-object') + gc.collect() + self.assertEqual(held.__grefcount__, initial_ref_count) + + def test_held_object_ref_count_setter(self): + holder = GIMarshallingTests.PropertiesObject() + held = GObject.Object() + + self.assertEqual(holder.__grefcount__, 1) + self.assertEqual(held.__grefcount__, 1) + + # Setting property should only increase ref count by 1 + self.set_prop(holder, 'some-object', held) + self.assertEqual(holder.__grefcount__, 1) + self.assertEqual(held.__grefcount__, 2) + + # Clearing should pull it back down + self.set_prop(holder, 'some-object', None) + self.assertEqual(held.__grefcount__, 1) + + def test_set_object_property_to_invalid_type(self): + obj = GIMarshallingTests.PropertiesObject() + self.assertRaises(TypeError, self.set_prop, obj, 'some-object', 'not_an_object') + + +class TestCPropsAccessor(CPropertiesTestBase, unittest.TestCase): + # C property tests using the "props" accessor. + def get_prop(self, obj, name): + return getattr(obj.props, name.replace('-', '_')) + + def set_prop(self, obj, name, value): + setattr(obj.props, name.replace('-', '_'), value) + + def test_props_accessor_dir(self): + # Test class + props = dir(GIMarshallingTests.PropertiesObject.props) + self.assertTrue('some_float' in props) + self.assertTrue('some_double' in props) + self.assertTrue('some_variant' in props) + + # Test instance + obj = GIMarshallingTests.PropertiesObject() + props = dir(obj.props) + self.assertTrue('some_float' in props) + self.assertTrue('some_double' in props) + self.assertTrue('some_variant' in props) + + def test_param_spec_dir(self): + attrs = dir(GIMarshallingTests.PropertiesObject.props.some_float) + self.assertTrue('name' in attrs) + self.assertTrue('nick' in attrs) + self.assertTrue('blurb' in attrs) + self.assertTrue('flags' in attrs) + self.assertTrue('default_value' in attrs) + self.assertTrue('minimum' in attrs) + self.assertTrue('maximum' in attrs) + + +class TestCGetPropertyMethod(CPropertiesTestBase, unittest.TestCase): + # C property tests using the "props" accessor. + def get_prop(self, obj, name): + return obj.get_property(name) + + def set_prop(self, obj, name, value): + obj.set_property(name, value) diff --git a/tests/test_pycapi.py b/tests/test_pycapi.py new file mode 100644 index 0000000..d049a8d --- /dev/null +++ b/tests/test_pycapi.py @@ -0,0 +1,45 @@ +import unittest +import ctypes +from ctypes import c_void_p, py_object, c_char_p + +import gi +from gi.repository import Gio + + +def get_capi(): + + if not hasattr(ctypes, "pythonapi"): + return + + class CAPI(ctypes.Structure): + _fields_ = [ + ("", c_void_p), + ("", c_void_p), + ("", c_void_p), + ("newgobj", ctypes.PYFUNCTYPE(py_object, c_void_p)), + ] + + api_obj = gi._gobject._PyGObject_API + func_type = ctypes.PYFUNCTYPE(c_void_p, py_object, c_char_p) + PyCapsule_GetPointer = func_type( + ('PyCapsule_GetPointer', ctypes.pythonapi)) + ptr = PyCapsule_GetPointer(api_obj, b"gobject._PyGObject_API") + + ptr = ctypes.cast(ptr, ctypes.POINTER(CAPI)) + return ptr.contents + + +API = get_capi() + + +@unittest.skipUnless(API, "no pythonapi support") +class TestPythonCAPI(unittest.TestCase): + + def test_newgobj(self): + w = Gio.FileInfo() + # XXX: ugh :/ + ptr = int(repr(w).split()[-1].split(")")[0], 16) + + capi = get_capi() + new_w = capi.newgobj(ptr) + assert w == new_w diff --git a/tests/test_pygtkcompat.py b/tests/test_pygtkcompat.py new file mode 100644 index 0000000..c01892a --- /dev/null +++ b/tests/test_pygtkcompat.py @@ -0,0 +1,339 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab + +import unittest +import base64 + +import pytest +import gi +import pygtkcompat +from pygtkcompat.pygtkcompat import _disable_all as disable_all + +from .helper import capture_gi_deprecation_warnings, capture_glib_warnings + +try: + from gi.repository import Gtk, Gdk +except ImportError: + Gtk = None +else: + if Gtk._version != "3.0": + Gtk = None + + +class TestGlibCompat(unittest.TestCase): + + def setUp(self): + pygtkcompat.enable() + + def tearDown(self): + disable_all() + + def test_import(self): + import glib + import gio + glib, gio + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestMultipleEnable(unittest.TestCase): + + def tearDown(self): + disable_all() + + def test_main(self): + pygtkcompat.enable() + pygtkcompat.enable() + + def test_gtk(self): + pygtkcompat.enable_gtk("3.0") + pygtkcompat.enable_gtk("3.0") + import gtk + + # https://bugzilla.gnome.org/show_bug.cgi?id=759009 + w = gtk.Window() + w.realize() + self.assertEqual(len(w.window.get_origin()), 2) + w.destroy() + + def test_gtk_no_4(self): + self.assertRaises(ValueError, pygtkcompat.enable_gtk, version='4.0') + + def test_gtk_version_conflict(self): + pygtkcompat.enable_gtk("3.0") + self.assertRaises(ValueError, pygtkcompat.enable_gtk, version='2.0') + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestATKCompat(unittest.TestCase): + + def setUp(self): + pygtkcompat.enable_gtk("3.0") + + def tearDown(self): + disable_all() + + def test_object(self): + import atk + self.assertTrue(hasattr(atk, 'Object')) + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestPangoCompat(unittest.TestCase): + + def setUp(self): + pygtkcompat.enable_gtk("3.0") + + def tearDown(self): + disable_all() + + def test_layout(self): + import pango + self.assertTrue(hasattr(pango, 'Layout')) + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestPangoCairoCompat(unittest.TestCase): + + def setUp(self): + pygtkcompat.enable_gtk("3.0") + + def tearDown(self): + disable_all() + + def test_error_underline_path(self): + import pangocairo + self.assertTrue(hasattr(pangocairo, 'error_underline_path')) + + +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestGTKCompat(unittest.TestCase): + + def setUp(self): + pygtkcompat.enable_gtk("3.0") + + def tearDown(self): + disable_all() + + def test_window_get_frame_extents(self): + import gtk + import gtk.gdk + w = gtk.Window() + w.realize() + rect = w.window.get_frame_extents() + assert isinstance(rect, gtk.gdk.Rectangle) + + def test_window_get_geometry(self): + import gtk + w = gtk.Window() + w.realize() + with capture_gi_deprecation_warnings(): + geo = w.window.get_geometry() + assert isinstance(geo, tuple) + assert len(geo) == 5 + + def test_action_set_tool_item_type(self): + import gtk + with pytest.warns(gi.PyGIDeprecationWarning): + gtk.Action().set_tool_item_type(gtk.Action) + + def test_treeviewcolumn_pack(self): + import gtk + col = gtk.TreeViewColumn() + col.pack_end(gtk.CellRendererText()) + col.pack_start(gtk.CellRendererText()) + + def test_cell_layout_pack(self): + import gtk + layout = gtk.EntryCompletion() + layout.pack_end(gtk.CellRendererText()) + layout.pack_start(gtk.CellRendererText()) + + def test_cell_layout_cell_data_func(self): + import gtk + + def func(*args): + pass + + layout = gtk.EntryCompletion() + render = gtk.CellRendererText() + layout.set_cell_data_func(render, func) + + def test_combo_row_separator_func(self): + import gtk + + def func(*args): + pass + + combo = gtk.ComboBox() + combo.set_row_separator_func(func) + + def test_container_install_child_property(self): + import gtk + + box = gtk.Box() + with pytest.warns(gi.PyGIDeprecationWarning): + box.install_child_property(0, None) + + def test_combo_box_new_text(self): + import gtk + + combo = gtk.combo_box_new_text() + assert isinstance(combo, gtk.ComboBox) + combo.append_text("foo") + + def test_scale(self): + import gtk + + adjustment = gtk.Adjustment() + assert gtk.HScale() + assert gtk.HScale(adjustment).get_adjustment() == adjustment + adjustment = gtk.Adjustment() + assert gtk.VScale() + assert gtk.VScale(adjustment).get_adjustment() == adjustment + + def test_stock_add(self): + import gtk + + gtk.stock_add([]) + + def test_text_view_scroll_to_mark(self): + import gtk + + view = gtk.TextView() + buf = view.get_buffer() + mark = gtk.TextMark(name="foo") + buf.add_mark(mark, buf.get_end_iter()) + view.scroll_to_mark(mark, 0.0) + + def test_window_set_geometry_hints(self): + import gtk + + w = gtk.Window() + w.set_geometry_hints(None, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + w.set_geometry_hints(None, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1) + with pytest.raises(TypeError): + w.set_geometry_hints(None, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + def test_buttons(self): + import gtk.gdk + self.assertEqual(gtk.gdk._2BUTTON_PRESS, 5) + self.assertEqual(gtk.gdk.BUTTON_PRESS, 4) + + def test_enums(self): + import gtk + self.assertEqual(gtk.WINDOW_TOPLEVEL, Gtk.WindowType.TOPLEVEL) + self.assertEqual(gtk.PACK_START, Gtk.PackType.START) + + def test_flags(self): + import gtk + self.assertEqual(gtk.EXPAND, Gtk.AttachOptions.EXPAND) + self.assertEqual(gtk.gdk.SHIFT_MASK, Gdk.ModifierType.SHIFT_MASK) + + def test_keysyms(self): + import gtk.keysyms + self.assertEqual(gtk.keysyms.Escape, Gdk.KEY_Escape) + self.assertTrue(gtk.keysyms._0, Gdk.KEY_0) + + def test_style(self): + import gtk + widget = gtk.Button() + with capture_gi_deprecation_warnings(): + widget.get_style_context().set_state(gtk.STATE_NORMAL) + self.assertTrue(isinstance(widget.style.base[gtk.STATE_NORMAL], + gtk.gdk.Color)) + + def test_alignment(self): + import gtk + # Creation of pygtk.Alignment causes hard warnings, ignore this in testing. + with capture_glib_warnings(allow_warnings=True): + a = gtk.Alignment() + + self.assertEqual(a.props.xalign, 0.0) + self.assertEqual(a.props.yalign, 0.0) + self.assertEqual(a.props.xscale, 0.0) + self.assertEqual(a.props.yscale, 0.0) + + def test_box(self): + import gtk + box = gtk.Box() + child = gtk.Button() + + box.pack_start(child) + expand, fill, padding, pack_type = box.query_child_packing(child) + self.assertTrue(expand) + self.assertTrue(fill) + self.assertEqual(padding, 0) + self.assertEqual(pack_type, gtk.PACK_START) + + child = gtk.Button() + box.pack_end(child) + expand, fill, padding, pack_type = box.query_child_packing(child) + self.assertTrue(expand) + self.assertTrue(fill) + self.assertEqual(padding, 0) + self.assertEqual(pack_type, gtk.PACK_END) + + def test_combobox_entry(self): + import gtk + liststore = gtk.ListStore(int, str) + liststore.append((1, 'One')) + liststore.append((2, 'Two')) + liststore.append((3, 'Three')) + # might cause a Pango warning, do not break on this + with capture_glib_warnings(allow_warnings=True): + combo = gtk.ComboBoxEntry(model=liststore) + combo.set_text_column(1) + combo.set_active(0) + self.assertEqual(combo.get_text_column(), 1) + self.assertEqual(combo.get_child().get_text(), 'One') + combo = gtk.combo_box_entry_new() + combo.set_model(liststore) + combo.set_text_column(1) + combo.set_active(0) + self.assertEqual(combo.get_text_column(), 1) + self.assertEqual(combo.get_child().get_text(), 'One') + combo = gtk.combo_box_entry_new_with_model(liststore) + combo.set_text_column(1) + combo.set_active(0) + self.assertEqual(combo.get_text_column(), 1) + self.assertEqual(combo.get_child().get_text(), 'One') + + def test_size_request(self): + import gtk + box = gtk.Box() + with capture_gi_deprecation_warnings(): + self.assertEqual(box.size_request(), [0, 0]) + + def test_pixbuf(self): + import gtk.gdk + gtk.gdk.Pixbuf() + + def test_pixbuf_loader(self): + import gtk.gdk + # load a 1x1 pixel PNG from memory + data = base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4n8Dw' + 'HwAGIAJf85Z3XgAAAABJRU5ErkJggg==') + loader = gtk.gdk.PixbufLoader('png') + loader.write(data) + loader.close() + + pixbuf = loader.get_pixbuf() + self.assertEqual(pixbuf.get_width(), 1) + self.assertEqual(pixbuf.get_height(), 1) + + def test_pixbuf_formats(self): + import gtk.gdk + formats = gtk.gdk.pixbuf_get_formats() + self.assertEqual(type(formats[0]), dict) + self.assertTrue('name' in formats[0]) + self.assertTrue('description' in formats[0]) + self.assertTrue('mime_types' in formats[0]) + self.assertEqual(type(formats[0]['extensions']), list) + + def test_gdk_window(self): + import gtk + w = gtk.Window() + w.realize() + origin = w.get_window().get_origin() + self.assertTrue(isinstance(origin, tuple)) + self.assertEqual(len(origin), 2) diff --git a/tests/test_repository.py b/tests/test_repository.py new file mode 100644 index 0000000..4390ff0 --- /dev/null +++ b/tests/test_repository.py @@ -0,0 +1,403 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab +# +# Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org> +# +# test_repository.py: Test for the GIRepository module +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + + +import unittest +from collections import abc + +import gi._gi as GIRepository +from gi.module import repository as repo +from gi.repository import GObject +from gi.repository import GIMarshallingTests +from gi.repository import GIRepository as IntrospectedRepository + +from .helper import capture_glib_warnings + + +def find_child_info(info, getter_name, name): + getter = getattr(info, getter_name) + for child in getter(): + if child.get_name() == name: + return child + else: + raise ValueError('child info %s not found' % name) + + +class Test(unittest.TestCase): + def setUp(self): + repo.require('GLib') + repo.require('GObject') + repo.require('GIMarshallingTests') + + def test_repo_get_dependencies(self): + self.assertRaises(TypeError, repo.get_dependencies) + self.assertEqual(repo.get_dependencies("GLib"), []) + self.assertEqual(repo.get_dependencies("GObject"), ["GLib-2.0"]) + + def test_repo_is_registered(self): + self.assertRaises(TypeError, repo.is_registered) + self.assertRaises(TypeError, repo.is_registered, None) + self.assertTrue(repo.is_registered("GIRepository")) + self.assertTrue(repo.is_registered("GIRepository", None)) + self.assertTrue(isinstance(repo.is_registered("GIRepository"), bool)) + self.assertTrue(repo.is_registered("GIRepository", "2.0")) + self.assertFalse(repo.is_registered("GIRepository", "")) + self.assertFalse(repo.is_registered("GIRepository", "99.0")) + self.assertFalse(repo.is_registered("GIRepository", "1.0")) + + def test_repo_get_immediate_dependencies(self): + self.assertRaises(TypeError, repo.get_immediate_dependencies) + self.assertEqual(repo.get_immediate_dependencies("GLib"), []) + self.assertEqual( + repo.get_immediate_dependencies("GObject"), ["GLib-2.0"]) + self.assertEqual( + repo.get_immediate_dependencies(namespace="GObject"), ["GLib-2.0"]) + self.assertEqual( + repo.get_immediate_dependencies("GIMarshallingTests"), ["Gio-2.0"]) + + def test_arg_info(self): + func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct') + args = func_info.get_arguments() + self.assertTrue(len(args), 1) + + arg = args[0] + self.assertEqual(arg.get_container(), func_info) + self.assertEqual(arg.get_direction(), GIRepository.Direction.OUT) + self.assertEqual(arg.get_name(), 'structs') + self.assertEqual(arg.get_namespace(), 'GIMarshallingTests') + self.assertFalse(arg.is_caller_allocates()) + self.assertFalse(arg.is_optional()) + self.assertFalse(arg.is_return_value()) + self.assertFalse(arg.may_be_null()) + self.assertEqual(arg.get_destroy(), -1) + self.assertEqual(arg.get_ownership_transfer(), GIRepository.Transfer.NOTHING) + self.assertEqual(arg.get_scope(), GIRepository.ScopeType.INVALID) + self.assertEqual(arg.get_type().get_tag(), GIRepository.TypeTag.ARRAY) + + def test_base_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + self.assertEqual(info.__name__, 'Object') + self.assertEqual(info.get_name(), 'Object') + self.assertEqual(info.__module__, 'gi.repository.GIMarshallingTests') + self.assertEqual(info.get_name_unescaped(), 'Object') + self.assertEqual(info.get_namespace(), 'GIMarshallingTests') + self.assertEqual(info.get_container(), None) + info2 = repo.find_by_name('GIMarshallingTests', 'Object') + self.assertFalse(info is info2) + self.assertEqual(info, info2) + self.assertTrue(info.equal(info2)) + assert isinstance(info.is_deprecated(), bool) + assert isinstance(info.get_type(), int) + assert info.get_attribute("nopenope") is None + + def test_object_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + self.assertEqual(info.get_parent(), repo.find_by_name('GObject', 'Object')) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertTrue(isinstance(info.get_fields(), abc.Iterable)) + self.assertTrue(isinstance(info.get_interfaces(), abc.Iterable)) + self.assertTrue(isinstance(info.get_constants(), abc.Iterable)) + self.assertTrue(isinstance(info.get_vfuncs(), abc.Iterable)) + self.assertTrue(isinstance(info.get_properties(), abc.Iterable)) + self.assertFalse(info.get_abstract()) + self.assertEqual(info.get_class_struct(), repo.find_by_name('GIMarshallingTests', 'ObjectClass')) + self.assertEqual(info.get_type_name(), 'GIMarshallingTestsObject') + self.assertEqual(info.get_type_init(), 'gi_marshalling_tests_object_get_type') + self.assertFalse(info.get_fundamental()) + self.assertEqual(info.get_parent(), repo.find_by_name('GObject', 'Object')) + assert info.find_vfunc("nopenope") is None + vfunc = info.find_vfunc("method_int8_out") + assert isinstance(vfunc, GIRepository.VFuncInfo) + + def test_callable_inheritance(self): + self.assertTrue(issubclass(GIRepository.CallableInfo, GIRepository.BaseInfo)) + self.assertTrue(issubclass(GIRepository.CallbackInfo, GIRepository.CallableInfo)) + self.assertTrue(issubclass(GIRepository.FunctionInfo, GIRepository.CallableInfo)) + self.assertTrue(issubclass(GIRepository.VFuncInfo, GIRepository.CallableInfo)) + self.assertTrue(issubclass(GIRepository.SignalInfo, GIRepository.CallableInfo)) + + def test_registered_type_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + # Call these from the class because GIObjectInfo overrides them + self.assertEqual(GIRepository.RegisteredTypeInfo.get_g_type(info), + GObject.type_from_name('GIMarshallingTestsObject')) + self.assertEqual(GIRepository.RegisteredTypeInfo.get_type_name(info), + 'GIMarshallingTestsObject') + self.assertEqual(GIRepository.RegisteredTypeInfo.get_type_init(info), + 'gi_marshalling_tests_object_get_type') + + def test_fundamental_object_info(self): + repo.require('Regress') + info = repo.find_by_name('Regress', 'TestFundamentalObject') + self.assertTrue(info.get_abstract()) + self.assertTrue(info.get_fundamental()) + self.assertEqual(info.get_ref_function(), 'regress_test_fundamental_object_ref') + self.assertEqual(info.get_unref_function(), 'regress_test_fundamental_object_unref') + self.assertEqual(info.get_get_value_function(), 'regress_test_value_get_fundamental_object') + self.assertEqual(info.get_set_value_function(), 'regress_test_value_set_fundamental_object') + + def test_interface_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Interface') + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertTrue(isinstance(info.get_vfuncs(), abc.Iterable)) + self.assertTrue(isinstance(info.get_constants(), abc.Iterable)) + self.assertTrue(isinstance(info.get_prerequisites(), abc.Iterable)) + self.assertTrue(isinstance(info.get_properties(), abc.Iterable)) + self.assertTrue(isinstance(info.get_signals(), abc.Iterable)) + + method = info.find_method('test_int8_in') + vfunc = info.find_vfunc('test_int8_in') + self.assertEqual(method.get_name(), 'test_int8_in') + self.assertEqual(vfunc.get_invoker(), method) + self.assertEqual(method.get_vfunc(), vfunc) + + iface = info.get_iface_struct() + self.assertEqual(iface, repo.find_by_name('GIMarshallingTests', 'InterfaceIface')) + + assert info.find_signal("nopenope") is None + + def test_struct_info(self): + info = repo.find_by_name('GIMarshallingTests', 'InterfaceIface') + self.assertTrue(isinstance(info, GIRepository.StructInfo)) + self.assertTrue(isinstance(info.get_fields(), abc.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertTrue(isinstance(info.get_size(), int)) + self.assertTrue(isinstance(info.get_alignment(), int)) + self.assertTrue(info.is_gtype_struct()) + self.assertFalse(info.is_foreign()) + + info = repo.find_by_name('GIMarshallingTests', 'SimpleStruct') + assert info.find_method("nope") is None + assert isinstance(info.find_method("method"), GIRepository.FunctionInfo) + + assert info.find_field("nope") is None + assert isinstance(info.find_field("int8"), GIRepository.FieldInfo) + + def test_enum_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Enum') + self.assertTrue(isinstance(info, GIRepository.EnumInfo)) + self.assertTrue(isinstance(info.get_values(), abc.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertFalse(info.is_flags()) + self.assertTrue(info.get_storage_type() > 0) # might be platform dependent + + def test_union_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Union') + self.assertTrue(isinstance(info, GIRepository.UnionInfo)) + self.assertTrue(isinstance(info.get_fields(), abc.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertTrue(isinstance(info.get_size(), int)) + self.assertTrue(isinstance(info.get_alignment(), int)) + + def test_type_info(self): + func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct') + arg_info, = func_info.get_arguments() + type_info = arg_info.get_type() + + self.assertTrue(type_info.is_pointer()) + self.assertEqual(type_info.get_tag(), GIRepository.TypeTag.ARRAY) + self.assertEqual(type_info.get_tag_as_string(), 'array') + self.assertEqual(type_info.get_param_type(0).get_tag(), + GIRepository.TypeTag.INTERFACE) + self.assertEqual(type_info.get_param_type(0).get_interface(), + repo.find_by_name('GIMarshallingTests', 'SimpleStruct')) + self.assertEqual(type_info.get_interface(), None) + self.assertEqual(type_info.get_array_length(), -1) + self.assertEqual(type_info.get_array_fixed_size(), 2) + self.assertFalse(type_info.is_zero_terminated()) + self.assertEqual(type_info.get_array_type(), GIRepository.ArrayType.C) + + def test_field_info(self): + info = repo.find_by_name('GIMarshallingTests', 'InterfaceIface') + field = find_child_info(info, 'get_fields', 'test_int8_in') + self.assertEqual(field.get_name(), 'test_int8_in') + self.assertTrue(field.get_flags() & GIRepository.FieldInfoFlags.IS_READABLE) + self.assertFalse(field.get_flags() & GIRepository.FieldInfoFlags.IS_WRITABLE) + self.assertEqual(field.get_type().get_tag(), GIRepository.TypeTag.INTERFACE) + + # don't test actual values because that might fail with architecture differences + self.assertTrue(isinstance(field.get_size(), int)) + self.assertTrue(isinstance(field.get_offset(), int)) + + def test_property_info(self): + info = repo.find_by_name('GIMarshallingTests', 'PropertiesObject') + prop = find_child_info(info, 'get_properties', 'some-object') + + flags = GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT + self.assertEqual(prop.get_flags(), flags) + self.assertEqual(prop.get_type().get_tag(), GIRepository.TypeTag.INTERFACE) + self.assertEqual(prop.get_type().get_interface(), + repo.find_by_name('GObject', 'Object')) + self.assertEqual(prop.get_ownership_transfer(), GIRepository.Transfer.NOTHING) + + def test_callable_info(self): + func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct') + self.assertTrue(hasattr(func_info, 'invoke')) + self.assertTrue(isinstance(func_info.get_arguments(), abc.Iterable)) + self.assertEqual(func_info.get_caller_owns(), GIRepository.Transfer.NOTHING) + self.assertFalse(func_info.may_return_null()) + self.assertEqual(func_info.get_return_type().get_tag(), GIRepository.TypeTag.VOID) + self.assertRaises(AttributeError, func_info.get_return_attribute, '_not_an_attr') + + def test_signal_info(self): + repo.require('Regress') + info = repo.find_by_name('Regress', 'TestObj') + sig_info = find_child_info(info, 'get_signals', 'test') + + sig_flags = GObject.SignalFlags.RUN_LAST | \ + GObject.SignalFlags.NO_RECURSE | GObject.SignalFlags.NO_HOOKS + + self.assertTrue(sig_info is not None) + self.assertTrue(isinstance(sig_info, GIRepository.CallableInfo)) + self.assertTrue(isinstance(sig_info, GIRepository.SignalInfo)) + self.assertEqual(sig_info.get_name(), 'test') + self.assertEqual(sig_info.get_class_closure(), None) + self.assertFalse(sig_info.true_stops_emit()) + self.assertEqual(sig_info.get_flags(), sig_flags) + + def test_notify_signal_info_with_obj(self): + repo.require('Regress') + info = repo.find_by_name('Regress', 'TestObj') + sig_info = find_child_info(info, 'get_signals', 'sig-with-array-prop') + + sig_flags = GObject.SignalFlags.RUN_LAST + + self.assertTrue(sig_info is not None) + self.assertTrue(isinstance(sig_info, GIRepository.CallableInfo)) + self.assertTrue(isinstance(sig_info, GIRepository.SignalInfo)) + self.assertEqual(sig_info.get_name(), 'sig-with-array-prop') + self.assertEqual(sig_info.get_class_closure(), None) + self.assertFalse(sig_info.true_stops_emit()) + self.assertEqual(sig_info.get_flags(), sig_flags) + + def test_object_constructor(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + method = find_child_info(info, 'get_methods', 'new') + + self.assertTrue(isinstance(method, GIRepository.CallableInfo)) + self.assertTrue(isinstance(method, GIRepository.FunctionInfo)) + self.assertTrue(method in info.get_methods()) + self.assertEqual(method.get_name(), 'new') + self.assertFalse(method.is_method()) + self.assertTrue(method.is_constructor()) + self.assertEqual(method.get_symbol(), 'gi_marshalling_tests_object_new') + + flags = method.get_flags() + self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_METHOD) + self.assertTrue(flags & GIRepository.FunctionInfoFlags.IS_CONSTRUCTOR) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_GETTER) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_SETTER) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.WRAPS_VFUNC) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.THROWS) + + def test_method_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + method = find_child_info(info, 'get_methods', 'vfunc_return_value_only') + + self.assertTrue(isinstance(method, GIRepository.CallableInfo)) + self.assertTrue(isinstance(method, GIRepository.FunctionInfo)) + self.assertTrue(method in info.get_methods()) + self.assertEqual(method.get_name(), 'vfunc_return_value_only') + self.assertFalse(method.is_constructor()) + self.assertEqual(method.get_symbol(), 'gi_marshalling_tests_object_vfunc_return_value_only') + self.assertTrue(method.is_method()) + + flags = method.get_flags() + self.assertTrue(flags & GIRepository.FunctionInfoFlags.IS_METHOD) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_CONSTRUCTOR) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_GETTER) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_SETTER) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.WRAPS_VFUNC) + self.assertFalse(flags & GIRepository.FunctionInfoFlags.THROWS) + + def test_vfunc_info(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + invoker = find_child_info(info, 'get_methods', 'vfunc_return_value_only') + vfunc = find_child_info(info, 'get_vfuncs', 'vfunc_return_value_only') + + self.assertTrue(isinstance(vfunc, GIRepository.CallableInfo)) + self.assertTrue(isinstance(vfunc, GIRepository.VFuncInfo)) + self.assertEqual(vfunc.get_name(), 'vfunc_return_value_only') + self.assertEqual(vfunc.get_invoker(), invoker) + self.assertEqual(invoker, info.find_method('vfunc_return_value_only')) + self.assertEqual(vfunc.get_flags(), 0) + self.assertEqual(vfunc.get_offset(), 0xFFFF) # unknown offset + self.assertEqual(vfunc.get_signal(), None) + + def test_callable_can_throw_gerror(self): + info = repo.find_by_name('GIMarshallingTests', 'Object') + invoker = find_child_info(info, 'get_methods', 'vfunc_meth_with_error') + vfunc = find_child_info(info, 'get_vfuncs', 'vfunc_meth_with_err') + + self.assertTrue(invoker.can_throw_gerror()) + self.assertTrue(vfunc.can_throw_gerror()) + + # Test that args do not include the GError** + self.assertEqual(len(invoker.get_arguments()), 1) + self.assertEqual(len(vfunc.get_arguments()), 1) + + # Sanity check method that does not throw. + invoker_no_throws = find_child_info(info, 'get_methods', 'method_int8_in') + self.assertFalse(invoker_no_throws.can_throw_gerror()) + + def test_flags_double_registration_error(self): + # a warning is printed for double registration and pygobject will + # also raise a RuntimeError. + GIMarshallingTests.NoTypeFlags # cause flags registration + info = repo.find_by_name('GIMarshallingTests', 'NoTypeFlags') + with capture_glib_warnings(allow_warnings=True, allow_criticals=True): + self.assertRaises(RuntimeError, + GIRepository.flags_register_new_gtype_and_add, + info) + + def test_enum_double_registration_error(self): + # a warning is printed for double registration and pygobject will + # also raise a RuntimeError. + GIMarshallingTests.Enum # cause enum registration + info = repo.find_by_name('GIMarshallingTests', 'Enum') + with capture_glib_warnings(allow_warnings=True, allow_criticals=True): + self.assertRaises(RuntimeError, + GIRepository.enum_register_new_gtype_and_add, + info) + + def test_enums(self): + self.assertTrue(hasattr(GIRepository, 'Direction')) + self.assertTrue(hasattr(GIRepository, 'Transfer')) + self.assertTrue(hasattr(GIRepository, 'ArrayType')) + self.assertTrue(hasattr(GIRepository, 'ScopeType')) + self.assertTrue(hasattr(GIRepository, 'VFuncInfoFlags')) + self.assertTrue(hasattr(GIRepository, 'FieldInfoFlags')) + self.assertTrue(hasattr(GIRepository, 'FunctionInfoFlags')) + self.assertTrue(hasattr(GIRepository, 'TypeTag')) + self.assertTrue(hasattr(GIRepository, 'InfoType')) + + def test_introspected_argument_info(self): + self.assertTrue(isinstance(IntrospectedRepository.Argument.__info__, + GIRepository.UnionInfo)) + + arg = IntrospectedRepository.Argument() + self.assertTrue(isinstance(arg.__info__, GIRepository.UnionInfo)) + + old_info = IntrospectedRepository.Argument.__info__ + IntrospectedRepository.Argument.__info__ = 'not an info' + self.assertRaises(TypeError, IntrospectedRepository.Argument) + IntrospectedRepository.Argument.__info__ = old_info diff --git a/tests/test_resulttuple.py b/tests/test_resulttuple.py new file mode 100644 index 0000000..00daa7b --- /dev/null +++ b/tests/test_resulttuple.py @@ -0,0 +1,89 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab +# +# Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + +import unittest +import pickle + +import gi +from gi.repository import GIMarshallingTests +from gi.repository import Regress + + +ResultTuple = gi._gi.ResultTuple + + +class TestResultTuple(unittest.TestCase): + + def test_base(self): + self.assertTrue(issubclass(ResultTuple, tuple)) + + def test_create(self): + names = [None, "foo", None, "bar"] + for i in range(10): + new = ResultTuple._new_type(names) + self.assertTrue(issubclass(new, ResultTuple)) + + def test_repr_dir(self): + new = ResultTuple._new_type([None, "foo", None, "bar"]) + inst = new([1, 2, 3, "a"]) + + self.assertEqual(repr(inst), "(1, foo=2, 3, bar='a')") + self.assertTrue("foo" in dir(inst)) + + def test_repr_dir_empty(self): + new = ResultTuple._new_type([]) + inst = new() + self.assertEqual(repr(inst), "()") + dir(inst) + + def test_getatttr(self): + new = ResultTuple._new_type([None, "foo", None, "bar"]) + inst = new([1, 2, 3, "a"]) + + self.assertTrue(hasattr(inst, "foo")) + self.assertEqual(inst.foo, inst[1]) + self.assertRaises(AttributeError, getattr, inst, "nope") + + def test_pickle(self): + new = ResultTuple._new_type([None, "foo", None, "bar"]) + inst = new([1, 2, 3, "a"]) + + inst2 = pickle.loads(pickle.dumps(inst)) + self.assertEqual(inst2, inst) + self.assertTrue(isinstance(inst2, tuple)) + self.assertFalse(isinstance(inst2, new)) + + def test_gi(self): + res = GIMarshallingTests.init_function([]) + self.assertEqual(repr(res), "(True, argv=[])") + + res = GIMarshallingTests.array_return_etc(5, 9) + self.assertEqual(repr(res), "([5, 0, 1, 9], sum=14)") + + res = GIMarshallingTests.array_out_etc(-5, 9) + self.assertEqual(repr(res), "(ints=[-5, 0, 1, 9], sum=4)") + + cb = lambda: (1, 2) + res = GIMarshallingTests.callback_multiple_out_parameters(cb) + self.assertEqual(repr(res), "(a=1.0, b=2.0)") + + def test_regress(self): + res = Regress.TestObj().skip_return_val(50, 42.0, 60, 2, 3) + self.assertEqual(repr(res), "(out_b=51, inout_d=61, out_sum=32)") diff --git a/tests/test_signal.py b/tests/test_signal.py index ec13896..8a935fd 100644 --- a/tests/test_signal.py +++ b/tests/test_signal.py @@ -3,11 +3,16 @@ import gc import unittest import sys +import weakref +import threading +import time + +from gi.repository import GObject, GLib, Regress, Gio +from gi import _signalhelper as signalhelper +from gi.module import repository as repo -from gi.repository import GObject, GLib -from gi._gobject import signalhelper import testhelper -from compathelper import _long +from .helper import capture_glib_warnings, capture_gi_deprecation_warnings class C(GObject.GObject): @@ -74,13 +79,9 @@ class TestGSignalsError(unittest.TestCase): def foo(): class Foo(GObject.GObject): __gsignals__ = {'not-exists': 'override'} - # do not stumble over the warning thrown by GLib - old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_CRITICAL | - GLib.LogLevelFlags.LEVEL_ERROR) - try: + + with capture_glib_warnings(allow_warnings=True): self.assertRaises(TypeError, foo) - finally: - GLib.log_set_always_fatal(old_mask) gc.collect() @@ -136,9 +137,9 @@ class TestAccumulator(unittest.TestCase): inst = Foo() inst.my_acc_signal.connect(lambda obj: 1) inst.my_acc_signal.connect(lambda obj: 2) - ## the value returned in the following handler will not be - ## considered, because at this point the accumulator already - ## reached its limit. + # the value returned in the following handler will not be + # considered, because at this point the accumulator already + # reached its limit. inst.my_acc_signal.connect(lambda obj: 3) retval = inst.my_acc_signal.emit() self.assertEqual(retval, 3) @@ -147,8 +148,8 @@ class TestAccumulator(unittest.TestCase): inst = Foo() inst.my_other_acc_signal.connect(self._true_handler1) inst.my_other_acc_signal.connect(self._true_handler2) - ## the following handler will not be called because handler2 - ## returns True, so it should stop the emission. + # the following handler will not be called because handler2 + # returns True, so it should stop the emission. inst.my_other_acc_signal.connect(self._true_handler3) self.__true_val = None inst.my_other_acc_signal.emit() @@ -327,9 +328,6 @@ class TestMatching(unittest.TestCase): self.assertEqual(obj.status, 2) def test_signal_handler_find(self): - def dummy(*args): - "Hack to work around: " - def foo(obj): obj.status += 1 @@ -340,7 +338,7 @@ class TestMatching(unittest.TestCase): found_id = GObject.signal_handler_find(obj, GObject.SignalMatchType.ID, signal_id=signal_id, detail=detail, - closure=None, func=dummy, data=dummy) + closure=None, func=0, data=0) self.assertEqual(handler_id, found_id) @@ -367,15 +365,10 @@ class TestClosures(unittest.TestCase): self.count += 1 def _callback_invalid_stop_emission_name(self, obj, prop): - # We expect a GLib warning but there currently is no way to test that - # This can at least make sure we don't crash - old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_CRITICAL | - GLib.LogLevelFlags.LEVEL_ERROR) - try: + with capture_glib_warnings(allow_warnings=True, allow_criticals=True) as warn: obj.stop_emission_by_name('notasignal::baddetail') - finally: - GLib.log_set_always_fatal(old_mask) self.emission_error = True + self.assertTrue(warn) def test_disconnect_by_func(self): e = E() @@ -410,7 +403,8 @@ class TestClosures(unittest.TestCase): e = E() e.connect('notify::prop', self._callback_invalid_stop_emission_name) - e.set_property('prop', 1234) + with capture_glib_warnings(): + e.set_property('prop', 1234) self.assertTrue(self.emission_error) def test_handler_block(self): @@ -494,7 +488,8 @@ class SigPropClass(GObject.GObject): (GObject.TYPE_INT,))} __gproperties__ = { - 'foo': (str, None, None, '', GObject.PARAM_WRITABLE | GObject.PARAM_CONSTRUCT), + 'foo': (str, None, None, '', + GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT), } signal_emission_failed = False @@ -525,7 +520,7 @@ class CM(GObject.GObject): test2=(GObject.SignalFlags.RUN_LAST, None, (str,)), test3=(GObject.SignalFlags.RUN_LAST, int, (GObject.TYPE_DOUBLE,)), test4=(GObject.SignalFlags.RUN_FIRST, None, - (bool, _long, GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE, int, + (bool, int, GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE, int, GObject.TYPE_UINT, GObject.TYPE_ULONG)), test_float=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_FLOAT, (GObject.TYPE_FLOAT,)), test_double=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_DOUBLE, (GObject.TYPE_DOUBLE,)), @@ -557,7 +552,7 @@ class _TestCMarshaller: self.assertEqual(rv, 20) def test_test4(self): - self.obj.emit("test4", True, _long(10), 3.14, 1.78, 20, _long(30), _long(31)) + self.obj.emit("test4", True, 10, 3.14, 1.78, 20, 30, 31) def test_float(self): rv = self.obj.emit("test-float", 1.234) @@ -571,11 +566,11 @@ class _TestCMarshaller: rv = self.obj.emit("test-int64", 102030405) self.assertEqual(rv, 102030405) - rv = self.obj.emit("test-int64", GObject.G_MAXINT64) - self.assertEqual(rv, GObject.G_MAXINT64 - 1) + rv = self.obj.emit("test-int64", GLib.MAXINT64) + self.assertEqual(rv, GLib.MAXINT64 - 1) - rv = self.obj.emit("test-int64", GObject.G_MININT64) - self.assertEqual(rv, GObject.G_MININT64) + rv = self.obj.emit("test-int64", GLib.MININT64) + self.assertEqual(rv, GLib.MININT64) def test_string(self): rv = self.obj.emit("test-string", "str") @@ -621,51 +616,48 @@ class _TestCMarshaller: # explicit float v = GObject.Value(GObject.TYPE_FLOAT, 1.234) rv = self.obj.emit("test-gvalue", v) - self.assertAlmostEqual(rv, 1.234, 4) + self.assertAlmostEqual(rv, 1.234, places=4) # implicit float rv = self.obj.emit("test-gvalue", 1.234) - self.assertAlmostEqual(rv, 1.234, 4) + self.assertAlmostEqual(rv, 1.234, places=4) # explicit int64 - v = GObject.Value(GObject.TYPE_INT64, GObject.G_MAXINT64) + v = GObject.Value(GObject.TYPE_INT64, GLib.MAXINT64) rv = self.obj.emit("test-gvalue", v) - self.assertEqual(rv, GObject.G_MAXINT64) - - # implicit int64 - # does not work, see https://bugzilla.gnome.org/show_bug.cgi?id=683775 - #rv = self.obj.emit("test-gvalue", GObject.G_MAXINT64) - #self.assertEqual(rv, GObject.G_MAXINT64) + self.assertEqual(rv, GLib.MAXINT64) # explicit uint64 - v = GObject.Value(GObject.TYPE_UINT64, GObject.G_MAXUINT64) + v = GObject.Value(GObject.TYPE_UINT64, GLib.MAXUINT64) rv = self.obj.emit("test-gvalue", v) - self.assertEqual(rv, GObject.G_MAXUINT64) + self.assertEqual(rv, GLib.MAXUINT64) + + @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=705291 + def test_gvalue_implicit_int64(self): + # implicit int64 + rv = self.obj.emit("test-gvalue", GLib.MAXINT64) + self.assertEqual(rv, GLib.MAXINT64) # implicit uint64 - # does not work, see https://bugzilla.gnome.org/show_bug.cgi?id=683775 - #rv = self.obj.emit("test-gvalue", GObject.G_MAXUINT64) - #self.assertEqual(rv, GObject.G_MAXUINT64) + rv = self.obj.emit("test-gvalue", GLib.MAXUINT64) + self.assertEqual(rv, GLib.MAXUINT64) def test_gvalue_ret(self): self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT), - GObject.G_MAXINT) + GLib.MAXINT) self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT), - GObject.G_MAXUINT) + GLib.MAXUINT) self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT64), - GObject.G_MAXINT64) + GLib.MAXINT64) self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT64), - GObject.G_MAXUINT64) + GLib.MAXUINT64) self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_STRING), "hello") -if 'generic-c-marshaller' in GObject.features: - class TestCMarshaller(_TestCMarshaller, unittest.TestCase): - pass -else: - print() - print('** WARNING: LIBFFI disabled, not testing') - print() + +class TestCMarshaller(_TestCMarshaller, unittest.TestCase): + pass + # Test for 374653 @@ -697,6 +689,10 @@ class TestSignalDecorator(unittest.TestCase): def pulled(self): self.value -= 1 + @GObject.Signal(flags=GObject.SignalFlags.DETAILED) + def detailed(self): + self.value -= 1 + stomped = GObject.Signal('stomped', arg_types=(int,), doc='this will stomp') unnamed = GObject.Signal() @@ -708,7 +704,6 @@ class TestSignalDecorator(unittest.TestCase): @GObject.SignalOverride def notify(self, *args, **kargs): self.overridden_closure_called = True - #GObject.GObject.notify(self, *args, **kargs) def on_notify(self, obj, prop): self.notify_called = True @@ -719,6 +714,26 @@ class TestSignalDecorator(unittest.TestCase): def onUnnamed(self, obj): self.unnamedCalled = True + def test_disconnect(self): + decorated = self.Decorated() + id_ = decorated.pushed.connect(lambda *args: None) + decorated.pushed.disconnect(id_) + + def test_signal_repr(self): + decorated = self.Decorated() + assert repr(decorated.pushed) == 'BoundSignal("pushed")' + + def test_signal_call(self): + decorated = self.Decorated() + assert decorated.value == 0 + decorated.pushed() + assert decorated.value == 1 + + def test_connect_detailed(self): + decorated = self.Decorated() + id_ = decorated.detailed.connect_detailed(lambda *args: None, "foo") + decorated.pushed.disconnect(id_) + def test_get_signal_args(self): self.assertEqual(self.Decorated.pushed.get_signal_args(), (GObject.SignalFlags.RUN_FIRST, None, tuple(), None, None)) @@ -760,13 +775,12 @@ class TestSignalDecorator(unittest.TestCase): obj.emit('unnamed') self.assertEqual(self.unnamedCalled, True) - def NOtest_overridden_signal(self): + def test_overridden_signal(self): # Test that the pushed signal is called in with super and the override # which should both increment the "value" to 3 obj = self.DecoratedOverride() obj.connect("notify", obj.on_notify) self.assertEqual(obj.value, 0) - #obj.notify.emit() obj.value = 1 self.assertEqual(obj.value, 1) self.assertTrue(obj.overridden_closure_called) @@ -847,6 +861,114 @@ class TestSignalConnectors(unittest.TestCase): self.assertEqual(self.value, 3) +class _ConnectDataTestBase(object): + # Notes: + # - self.Object is overridden in sub-classes. + # - Numeric suffixes indicate the number of user data args passed in. + Object = None + + def run_connect_test(self, emit_args, user_data, flags=0): + obj = self.Object() + callback_args = [] + + def callback(*args): + callback_args.append(args) + return 0 + + obj.connect_data('sig-with-int64-prop', callback, connect_flags=flags, *user_data) + obj.emit('sig-with-int64-prop', *emit_args) + self.assertEqual(len(callback_args), 1) + return callback_args[0] + + def test_0(self): + obj, value = self.run_connect_test([GLib.MAXINT64], user_data=[]) + self.assertIsInstance(obj, self.Object) + self.assertEqual(value, GLib.MAXINT64) + + def test_1(self): + obj, value, data = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata']) + self.assertIsInstance(obj, self.Object) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data, 'mydata') + + def test_after_0(self): + obj, value = self.run_connect_test([GLib.MAXINT64], + user_data=[], + flags=GObject.ConnectFlags.AFTER) + self.assertIsInstance(obj, self.Object) + self.assertEqual(value, GLib.MAXINT64) + + def test_after_1(self): + obj, value, data = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata'], + flags=GObject.ConnectFlags.AFTER) + self.assertIsInstance(obj, self.Object) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data, 'mydata') + + def test_swaped_0(self): + # Swapped only works with a single user data argument. + with self.assertRaises(ValueError): + self.run_connect_test([GLib.MAXINT64], + user_data=[], + flags=GObject.ConnectFlags.SWAPPED) + + def test_swaped_1(self): + # Notice obj and data are reversed in the return. + data, value, obj = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata'], + flags=GObject.ConnectFlags.SWAPPED) + self.assertIsInstance(obj, self.Object) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data, 'mydata') + + def test_swaped_2(self): + # Swapped only works with a single user data argument. + with self.assertRaises(ValueError): + self.run_connect_test([GLib.MAXINT64], + user_data=[1, 2], + flags=GObject.ConnectFlags.SWAPPED) + + def test_after_and_swapped_0(self): + # Swapped only works with a single user data argument. + with self.assertRaises(ValueError): + self.run_connect_test([GLib.MAXINT64], + user_data=[], + flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED) + + def test_after_and_swapped_1(self): + # Notice obj and data are reversed in the return. + data, value, obj = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata'], + flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED) + self.assertIsInstance(obj, self.Object) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data, 'mydata') + + def test_after_and_swapped_2(self): + # Swapped only works with a single user data argument. + with self.assertRaises(ValueError): + self.run_connect_test([GLib.MAXINT64], + user_data=[], + flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED) + + +class TestConnectDataNonIntrospected(unittest.TestCase, _ConnectDataTestBase): + # This tests connect_data with non-introspected signals + # (created in Python in this case). + class Object(GObject.Object): + test = GObject.Signal() + sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64, + arg_types=[GObject.TYPE_INT64], + flags=GObject.SignalFlags.RUN_LAST) + + +class TestConnectDataIntrospected(unittest.TestCase, _ConnectDataTestBase): + # This tests connect_data with introspected signals brought in from Regress. + Object = Regress.TestObj + + class TestInstallSignals(unittest.TestCase): # These tests only test how signalhelper.install_signals works # with the __gsignals__ dict and therefore does not need to use @@ -890,29 +1012,16 @@ class TestInstallSignals(unittest.TestCase): self.assertTrue(hasattr(self.Sub2, 'do_sub2test')) -# For this test to work with both python2 and 3 we need to dynamically -# exec the given code due to the new syntax causing an error in python 2. -annotated_class_code = """ -class AnnotatedSignalClass(GObject.GObject): - @GObject.Signal - def sig1(self, a:int, b:float): - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST) - def sig2_with_return(self, a:int, b:float) -> str: - return "test" -""" - - -@unittest.skipUnless(sys.version_info >= (3, 0), - 'Argument annotations require Python 3') class TestPython3Signals(unittest.TestCase): - AnnotatedClass = None - def setUp(self): - exec(annotated_class_code, globals(), globals()) - self.assertTrue('AnnotatedSignalClass' in globals()) - self.AnnotatedClass = globals()['AnnotatedSignalClass'] + class AnnotatedClass(GObject.GObject): + @GObject.Signal + def sig1(self, a: int, b: float): + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST) + def sig2_with_return(self, a: int, b: float) -> str: + return "test" def test_annotations(self): self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig1.func), @@ -981,5 +1090,473 @@ class TestSignalModuleLevelFunctions(unittest.TestCase): None) -if __name__ == '__main__': - unittest.main() +class TestIntrospectedSignals(unittest.TestCase): + def test_object_param_signal(self): + obj = Regress.TestObj() + + def callback(obj, obj_param): + self.assertEqual(obj_param.props.int, 3) + self.assertGreater(obj_param.__grefcount__, 1) + obj.called = True + + obj.called = False + obj.connect('sig-with-obj', callback) + obj.emit_sig_with_obj() + self.assertTrue(obj.called) + + def test_connect_after(self): + obj = Regress.TestObj() + + def callback(obj, obj_param): + obj.called = True + + obj.called = False + obj.connect_after('sig-with-obj', callback) + obj.emit_sig_with_obj() + self.assertTrue(obj.called) + + def test_int64_param_from_py(self): + obj = Regress.TestObj() + + def callback(obj, i): + obj.callback_i = i + return i + + obj.callback_i = None + obj.connect('sig-with-int64-prop', callback) + rv = obj.emit('sig-with-int64-prop', GLib.MAXINT64) + self.assertEqual(rv, GLib.MAXINT64) + self.assertEqual(obj.callback_i, GLib.MAXINT64) + + def test_uint64_param_from_py(self): + obj = Regress.TestObj() + + def callback(obj, i): + obj.callback_i = i + return i + + obj.callback_i = None + obj.connect('sig-with-uint64-prop', callback) + rv = obj.emit('sig-with-uint64-prop', GLib.MAXUINT64) + self.assertEqual(rv, GLib.MAXUINT64) + self.assertEqual(obj.callback_i, GLib.MAXUINT64) + + def test_int64_param_from_c(self): + obj = Regress.TestObj() + + def callback(obj, i): + obj.callback_i = i + return i + + obj.callback_i = None + + obj.connect('sig-with-int64-prop', callback) + obj.emit_sig_with_int64() + self.assertEqual(obj.callback_i, GLib.MAXINT64) + + def test_uint64_param_from_c(self): + obj = Regress.TestObj() + + def callback(obj, i): + obj.callback_i = i + return i + + obj.callback_i = None + + obj.connect('sig-with-uint64-prop', callback) + obj.emit_sig_with_uint64() + self.assertEqual(obj.callback_i, GLib.MAXUINT64) + + def test_intarray_ret(self): + obj = Regress.TestObj() + + def callback(obj, i): + obj.callback_i = i + return [i, i + 1] + + obj.callback_i = None + + try: + obj.connect('sig-with-intarray-ret', callback) + except TypeError as e: + # compat with g-i 1.34.x + if 'unknown signal' in str(e): + return + raise + + rv = obj.emit('sig-with-intarray-ret', 42) + self.assertEqual(obj.callback_i, 42) + self.assertEqual(type(rv), GLib.Array) + self.assertEqual(rv.len, 2) + + @unittest.skip('https://bugzilla.gnome.org/show_bug.cgi?id=669496') + def test_array_parm(self): + obj = Regress.TestObj() + + def callback(obj, arr): + obj.callback_arr = arr + + obj.connect('sig-with-array-prop', callback) + obj.callback_arr = None + self.assertEqual(obj.emit('sig-with-array-prop', [1, 2, GLib.MAXUINT]), None) + self.assertEqual(obj.callback_arr, [1, 2, GLib.MAXUINT]) + + def test_held_struct_ref(self): + held_structs = [] + + def callback(obj, struct): + # The struct held by Python will become a copy after this callback exits. + struct.some_int = 42 + struct.some_int8 = 42 + held_structs.append(struct) + + struct = Regress.TestSimpleBoxedA() + obj = Regress.TestObj() + + self.assertEqual(struct.some_int, 0) + self.assertEqual(struct.some_int8, 0) + + obj.connect('test-with-static-scope-arg', callback) + obj.emit('test-with-static-scope-arg', struct) + + # The held struct will be a copy of the modified struct. + self.assertEqual(len(held_structs), 1) + held_struct = held_structs[0] + self.assertEqual(held_struct.some_int, 42) + self.assertEqual(held_struct.some_int8, 42) + + # Boxed equality checks pointers by default. + self.assertNotEqual(struct, held_struct) + + def test_action(self): + obj = Regress.TestAction() + other_obj = obj.emit('action') + self.assertEqual(other_obj.__grefcount__, 1) + other_obj2 = obj.emit('action2') + self.assertIsNone(other_obj2) + + +class TestIntrospectedSignalsIssue158(unittest.TestCase): + """ + The test for https://gitlab.gnome.org/GNOME/pygobject/issues/158 + """ + _obj_sig_names = [sig.get_name() for sig in repo.find_by_name('Regress', 'TestObj').get_signals()] + + def __init__(self, *args): + unittest.TestCase.__init__(self, *args) + self._gc_thread_stop = False + + def _gc_thread(self): + while not self._gc_thread_stop: + gc.collect() + time.sleep(0.010) + + def _callback(self, *args): + pass + + def test_run(self): + """ + Manually trigger GC from a different thread periodicaly + while the main thread keeps connecting/disconnecting to/from signals. + + It takes a lot of time to reproduce the issue. It is possible to make it + fail reliably by changing the code of pygobject_unwatch_closure slightly from: + PyGObjectData *inst_data = data; + inst_data->closures = g_slist_remove (inst_data->closures, closure); + to + PyGObjectData *inst_data = data; + GSList *tmp = g_slist_remove (inst_data->closures, closure); + g_usleep(G_USEC_PER_SEC/10); + inst_data->closures = tmp; + """ + obj = Regress.TestObj() + gc_thread = threading.Thread(target=self._gc_thread) + gc_thread.start() + + for _ in range(8): + handlers = [obj.connect(sig, self._callback) for sig in self._obj_sig_names] + time.sleep(0.010) + while len(handlers) > 0: + obj.disconnect(handlers.pop()) + + self._gc_thread_stop = True + gc_thread.join() + + +class _ConnectObjectTestBase(object): + # Notes: + # - self.Object is overridden in sub-classes. + # - Numeric suffixes indicate the number of user data args passed in. + Object = None + SwapObject = None + + def run_connect_test(self, emit_args, user_data, flags=0): + obj = self.Object() + callback_args = [] + swap_obj = self.SwapObject() + + def callback(*args): + callback_args.append(args) + return 0 + + if flags & GObject.ConnectFlags.AFTER: + connect_func = obj.connect_object_after + else: + connect_func = obj.connect_object + + with capture_gi_deprecation_warnings(): + connect_func('sig-with-int64-prop', callback, swap_obj, *user_data) + obj.emit('sig-with-int64-prop', *emit_args) + self.assertEqual(len(callback_args), 1) + return callback_args[0] + + def test_0(self): + obj, value = self.run_connect_test([GLib.MAXINT64], user_data=[]) + self.assertIsInstance(obj, self.SwapObject) + self.assertEqual(value, GLib.MAXINT64) + + def test_1(self): + obj, value, data = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata']) + self.assertIsInstance(obj, self.SwapObject) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data, 'mydata') + + def test_2(self): + obj, value, data1, data2 = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata1', 'mydata2']) + self.assertIsInstance(obj, self.SwapObject) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data1, 'mydata1') + self.assertEqual(data2, 'mydata2') + + def test_after_0(self): + obj, value = self.run_connect_test([GLib.MAXINT64], + user_data=[], + flags=GObject.ConnectFlags.AFTER) + self.assertIsInstance(obj, self.SwapObject) + self.assertEqual(value, GLib.MAXINT64) + + def test_after_1(self): + obj, value, data = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata'], + flags=GObject.ConnectFlags.AFTER) + self.assertIsInstance(obj, self.SwapObject) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data, 'mydata') + + def test_after_2(self): + obj, value, data1, data2 = self.run_connect_test([GLib.MAXINT64], + user_data=['mydata1', 'mydata2'], + flags=GObject.ConnectFlags.AFTER) + self.assertIsInstance(obj, self.SwapObject) + self.assertEqual(value, GLib.MAXINT64) + self.assertEqual(data1, 'mydata1') + self.assertEqual(data2, 'mydata2') + + +class TestConnectGObjectNonIntrospected(unittest.TestCase, _ConnectObjectTestBase): + # This tests connect_object with non-introspected signals + # (created in Python in this case). + class Object(GObject.Object): + test = GObject.Signal() + sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64, + arg_types=[GObject.TYPE_INT64], + flags=GObject.SignalFlags.RUN_LAST) + + # Object passed for swapping is GObject based. + class SwapObject(GObject.Object): + pass + + +class TestConnectGObjectIntrospected(unittest.TestCase, _ConnectObjectTestBase): + # This tests connect_object with introspected signals brought in from Regress. + Object = Regress.TestObj + + # Object passed for swapping is GObject based. + class SwapObject(GObject.Object): + pass + + +class TestConnectPyObjectNonIntrospected(unittest.TestCase, _ConnectObjectTestBase): + # This tests connect_object with non-introspected signals + # (created in Python in this case). + class Object(GObject.Object): + test = GObject.Signal() + sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64, + arg_types=[GObject.TYPE_INT64], + flags=GObject.SignalFlags.RUN_LAST) + + # Object passed for swapping is pure Python + SwapObject = object + + +class TestConnectPyObjectIntrospected(unittest.TestCase, _ConnectObjectTestBase): + # This tests connect_object with introspected signals brought in from Regress. + Object = Regress.TestObj + + # Object passed for swapping is pure Python + SwapObject = object + + +class _RefCountTestBase(object): + # NOTE: ref counts are always one more than expected because the getrefcount() + # function adds a ref for the input argument. + + # Sub-classes set this + Object = None + + class PyData(object): + pass + + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") + def test_callback_ref_count_del(self): + def callback(obj, value): + return value // 2 + + callback_ref = weakref.ref(callback) + self.assertEqual(sys.getrefcount(callback), 2) + + obj = self.Object() + obj.connect('sig-with-int64-prop', callback) + self.assertEqual(sys.getrefcount(callback), 3) + + del callback + self.assertEqual(sys.getrefcount(callback_ref()), 2) + + res = obj.emit('sig-with-int64-prop', 42) + self.assertEqual(res, 21) + self.assertEqual(sys.getrefcount(callback_ref), 2) + + del obj + self.assertIsNone(callback_ref()) + + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") + def test_callback_ref_count_disconnect(self): + def callback(obj, value): + return value // 2 + + callback_ref = weakref.ref(callback) + self.assertEqual(sys.getrefcount(callback), 2) + + obj = self.Object() + handler_id = obj.connect('sig-with-int64-prop', callback) + self.assertEqual(sys.getrefcount(callback), 3) + + del callback + self.assertEqual(sys.getrefcount(callback_ref()), 2) + + res = obj.emit('sig-with-int64-prop', 42) + self.assertEqual(res, 21) + self.assertEqual(sys.getrefcount(callback_ref), 2) + + obj.disconnect(handler_id) + self.assertIsNone(callback_ref()) + + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") + def test_callback_ref_count_disconnect_by_func(self): + def callback(obj, value): + return value // 2 + + callback_ref = weakref.ref(callback) + self.assertEqual(sys.getrefcount(callback), 2) + + obj = self.Object() + obj.connect('sig-with-int64-prop', callback) + self.assertEqual(sys.getrefcount(callback), 3) + + del callback + self.assertEqual(sys.getrefcount(callback_ref()), 2) + + res = obj.emit('sig-with-int64-prop', 42) + self.assertEqual(res, 21) + self.assertEqual(sys.getrefcount(callback_ref), 2) + + obj.disconnect_by_func(callback_ref()) + self.assertIsNone(callback_ref()) + + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") + def test_user_data_ref_count(self): + def callback(obj, value, data): + return value // 2 + + data = self.PyData() + data_ref = weakref.ref(data) + self.assertEqual(sys.getrefcount(data), 2) + + obj = self.Object() + obj.connect('sig-with-int64-prop', callback, data) + self.assertEqual(sys.getrefcount(data), 3) + + del data + self.assertEqual(sys.getrefcount(data_ref()), 2) + + res = obj.emit('sig-with-int64-prop', 42) + self.assertEqual(res, 21) + self.assertEqual(sys.getrefcount(data_ref()), 2) + + del obj + self.assertIsNone(data_ref()) + + @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount") + @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=688064 + def test_object_ref_count(self): + # connect_object() should only weakly reference the object passed in + # and auto-disconnect the signal when the object is destroyed. + def callback(data, value): + return value // 2 + + data = GObject.Object() + data_ref = weakref.ref(data) + self.assertEqual(sys.getrefcount(data), 2) + + obj = self.Object() + handler_id = obj.connect_object('sig-with-int64-prop', callback, data) + self.assertEqual(sys.getrefcount(data), 2) + + res = obj.emit('sig-with-int64-prop', 42) + self.assertEqual(res, 21) + self.assertEqual(sys.getrefcount(data), 2) + + del data + + self.assertIsNone(data_ref()) + self.assertFalse(obj.handler_is_connected(handler_id)) + + +class TestRefCountsNonIntrospected(unittest.TestCase, _RefCountTestBase): + class Object(GObject.Object): + sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64, + arg_types=[GObject.TYPE_INT64], + flags=GObject.SignalFlags.RUN_LAST) + + +class TestRefCountsIntrospected(unittest.TestCase, _RefCountTestBase): + Object = Regress.TestObj + + +class TestClosureRefCycle(unittest.TestCase): + + def test_closure_ref_cycle_unreachable(self): + # https://bugzilla.gnome.org/show_bug.cgi?id=731501 + + called = [] + + def on_add(store, *args): + called.append(store) + + store = Gio.ListStore() + store.connect_object('items-changed', on_add, store) + + # Remove all Python references to the object and keep it alive + # on the C level. + x = Gio.FileInfo() + x.set_attribute_object('store', store) + del store + gc.collect() + + # get it back and trigger the signal + x.get_attribute_object('store').append(Gio.FileInfo()) + + self.assertEqual(len(called), 1) + self.assertTrue(called[0].__grefcount__ > 0) diff --git a/tests/test_source.py b/tests/test_source.py index d0e28e4..9249892 100644 --- a/tests/test_source.py +++ b/tests/test_source.py @@ -1,11 +1,15 @@ # -*- Mode: Python -*- +import sys +import gc import unittest import warnings -from gi.repository import GLib, GObject +from gi.repository import GLib from gi import PyGIDeprecationWarning +from .helper import capture_glib_warnings + class Idle(GLib.Idle): def __init__(self, loop): @@ -123,18 +127,24 @@ class TestSource(unittest.TestCase): return s s = f() + gc.collect() + gc.collect() self.assertTrue(s.is_destroyed()) def test_remove(self): s = GLib.idle_add(dir) self.assertEqual(GLib.source_remove(s), True) - # s is now removed, should fail now - self.assertEqual(GLib.source_remove(s), False) - # accepts large source IDs (they are unsigned) - self.assertEqual(GLib.source_remove(GObject.G_MAXINT32), False) - self.assertEqual(GLib.source_remove(GObject.G_MAXINT32 + 1), False) - self.assertEqual(GLib.source_remove(GObject.G_MAXUINT32), False) + # Removing sources not found cause critical + with capture_glib_warnings(allow_criticals=True): + + # s is now removed, should fail now + self.assertEqual(GLib.source_remove(s), False) + + # accepts large source IDs (they are unsigned) + self.assertEqual(GLib.source_remove(GLib.MAXINT32), False) + self.assertEqual(GLib.source_remove(GLib.MAXINT32 + 1), False) + self.assertEqual(GLib.source_remove(GLib.MAXUINT32), False) def test_recurse_property(self): s = GLib.Idle() @@ -183,6 +193,7 @@ class TestSource(unittest.TestCase): GLib.Timeout(20) GLib.Idle() + @unittest.skipIf(sys.platform == "darwin", "hangs") def test_finalize(self): self.dispatched = False self.finalized = False @@ -199,8 +210,7 @@ class TestSource(unittest.TestCase): self.finalized = True source = S() - id = source.attach() - print('source id:', id) + source.attach() self.assertFalse(self.finalized) self.assertFalse(source.is_destroyed()) @@ -212,8 +222,48 @@ class TestSource(unittest.TestCase): self.assertFalse(self.finalized) self.assertTrue(source.is_destroyed()) del source + gc.collect() + gc.collect() self.assertTrue(self.finalized) + def test_python_unref_with_active_source(self): + # Tests a Python derived Source which is free'd in the context of + # Python, but which was attached to a MainContext (via source.attach()) + self.dispatched = False + self.finalized = False + + class S(GLib.Source): + def prepare(s): + return (True, 1) + + def check(s): + pass + + def dispatch(s, callback, args): + self.dispatched = True + return False + + def finalize(s): + self.finalized = True + + context = GLib.MainContext.new() + source = S() + id_ = source.attach(context) + self.assertFalse(self.finalized) + self.assertFalse(source.is_destroyed()) + + # Delete the source from Python, it should detach + del source + gc.collect() + gc.collect() + + while context.iteration(may_block=False): + pass + + assert self.finalized + assert not self.dispatched + assert context.find_source_by_id(id_) is None + def test_extra_init_args(self): class SourceWithInitArgs(GLib.Source): def __init__(self, arg, kwarg=None): @@ -226,6 +276,7 @@ class TestSource(unittest.TestCase): self.assertEqual(source.kwarg, 2) +@unittest.skipIf(sys.platform == "darwin", "hangs") class TestUserData(unittest.TestCase): def test_idle_no_data(self): ml = GLib.MainLoop() @@ -242,7 +293,7 @@ class TestUserData(unittest.TestCase): def cb(): ml.quit() - id = GLib.timeout_add(50, cb) + id = GLib.timeout_add(1, cb) self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_DEFAULT) ml.run() @@ -282,7 +333,7 @@ class TestUserData(unittest.TestCase): data['called'] = True ml.quit() data = {} - id = GLib.timeout_add(50, cb, data) + id = GLib.timeout_add(1, cb, data) self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_DEFAULT) ml.run() @@ -296,7 +347,7 @@ class TestUserData(unittest.TestCase): data['data2'] = data2 ml.quit() data = {} - id = GLib.timeout_add(50, cb, data, 'hello') + id = GLib.timeout_add(1, cb, data, 'hello') self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_DEFAULT) ml.run() @@ -318,7 +369,7 @@ class TestUserData(unittest.TestCase): def cb(): ml.quit() - id = GLib.timeout_add(50, cb, priority=GLib.PRIORITY_HIGH) + id = GLib.timeout_add(1, cb, priority=GLib.PRIORITY_HIGH) self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) ml.run() @@ -343,7 +394,7 @@ class TestUserData(unittest.TestCase): data['called'] = True ml.quit() data = {} - id = GLib.timeout_add(50, cb, data, priority=GLib.PRIORITY_HIGH) + id = GLib.timeout_add(1, cb, data, priority=GLib.PRIORITY_HIGH) self.assertEqual(ml.get_context().find_source_by_id(id).priority, GLib.PRIORITY_HIGH) ml.run() @@ -367,7 +418,3 @@ class TestUserData(unittest.TestCase): GLib.idle_add(self.cb_with_data, data) self.loop.run() self.assertTrue(data['called']) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_subprocess.py b/tests/test_subprocess.py index ef4c35e..274a501 100644 --- a/tests/test_subprocess.py +++ b/tests/test_subprocess.py @@ -5,75 +5,91 @@ import os import unittest import warnings +import pytest + from gi.repository import GLib from gi import PyGIDeprecationWarning +from .helper import capture_gi_deprecation_warnings + + +def test_child_watch_add_get_args_various(): + cb = lambda pid, status: None + get_args = GLib._child_watch_add_get_args + pid = 42 + with capture_gi_deprecation_warnings(): + assert get_args(pid, cb, 2) == (0, pid, cb, (2,)) + + with pytest.raises(TypeError): + get_args(pid, cb, 2, 3, 4) + + assert get_args(0, pid, 2, 3, function=cb) == (0, pid, cb, (2, 3)) + assert get_args(0, pid, cb, 2, 3) == (0, pid, cb, (2, 3)) + assert get_args(0, pid, cb, data=99) == (0, pid, cb, (99,)) + with pytest.raises(TypeError): + get_args(0, pid, 24) + + with pytest.raises(TypeError): + get_args(0, pid, cb, 2, 3, data=99) + + +@unittest.skipIf(os.name == "nt", "not on Windows") class TestProcess(unittest.TestCase): def test_deprecated_child_watch_no_data(self): - def cb(pid, status): - self.status = status - self.loop.quit() - - self.status = None - self.loop = GLib.MainLoop() - argv = [sys.executable, '-c', 'import sys'] - pid, stdin, stdout, stderr = GLib.spawn_async( - argv, flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD) - pid.close() + cb = lambda pid, status: None + pid = object() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') - GLib.child_watch_add(pid, cb) + res = GLib._child_watch_add_get_args(pid, cb) self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning)) - self.loop.run() - self.assertEqual(self.status, 0) - def test_deprecated_child_watch_data_priority(self): - def cb(pid, status, data): - self.data = data - self.status = status - self.loop.quit() + self.assertEqual(len(res), 4) + self.assertEqual(res[0], GLib.PRIORITY_DEFAULT) + self.assertEqual(res[1], pid) + self.assertTrue(callable(cb)) + self.assertSequenceEqual(res[3], []) - self.status = None - self.data = None - self.loop = GLib.MainLoop() - argv = [sys.executable, '-c', 'import sys'] - pid, stdin, stdout, stderr = GLib.spawn_async( - argv, flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD) - pid.close() + def test_deprecated_child_watch_data_priority(self): + cb = lambda pid, status: None + pid = object() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') - id = GLib.child_watch_add(pid, cb, 12345, GLib.PRIORITY_HIGH) + res = GLib._child_watch_add_get_args(pid, cb, 12345, GLib.PRIORITY_HIGH) self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning)) - self.assertEqual(self.loop.get_context().find_source_by_id(id).priority, - GLib.PRIORITY_HIGH) - self.loop.run() - self.assertEqual(self.data, 12345) - self.assertEqual(self.status, 0) - def test_deprecated_child_watch_data_priority_kwargs(self): - def cb(pid, status, data): - self.data = data - self.status = status - self.loop.quit() + self.assertEqual(len(res), 4) + self.assertEqual(res[0], GLib.PRIORITY_HIGH) + self.assertEqual(res[1], pid) + self.assertEqual(res[2], cb) + self.assertSequenceEqual(res[3], [12345]) - self.status = None - self.data = None - self.loop = GLib.MainLoop() - argv = [sys.executable, '-c', 'import sys'] - pid, stdin, stdout, stderr = GLib.spawn_async( - argv, flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD) - pid.close() + def test_deprecated_child_watch_data_priority_kwargs(self): + cb = lambda pid, status: None + pid = object() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') - id = GLib.child_watch_add(pid, cb, priority=GLib.PRIORITY_HIGH, data=12345) + res = GLib._child_watch_add_get_args(pid, cb, priority=GLib.PRIORITY_HIGH, data=12345) self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning)) - self.assertEqual(self.loop.get_context().find_source_by_id(id).priority, - GLib.PRIORITY_HIGH) - self.loop.run() - self.assertEqual(self.data, 12345) - self.assertEqual(self.status, 0) + + self.assertEqual(len(res), 4) + self.assertEqual(res[0], GLib.PRIORITY_HIGH) + self.assertEqual(res[1], pid) + self.assertEqual(res[2], cb) + self.assertSequenceEqual(res[3], [12345]) + + @unittest.expectedFailure # using keyword args is fully supported by PyGObject machinery + def test_child_watch_all_kwargs(self): + cb = lambda pid, status: None + pid = object() + + res = GLib._child_watch_add_get_args(priority=GLib.PRIORITY_HIGH, pid=pid, function=cb, data=12345) + self.assertEqual(len(res), 4) + self.assertEqual(res[0], GLib.PRIORITY_HIGH) + self.assertEqual(res[1], pid) + self.assertEqual(res[2], cb) + self.assertSequenceEqual(res[3], [12345]) def test_child_watch_no_data(self): def cb(pid, status): @@ -130,6 +146,23 @@ class TestProcess(unittest.TestCase): self.assertEqual(out, b'hello world!\n') self.assertEqual(err, b'') + def test_spawn_async_with_pipes(self): + res, pid, stdin, stdout, stderr = GLib.spawn_async_with_pipes( + working_directory=None, + argv=['cat'], + envp=None, + flags=GLib.SpawnFlags.SEARCH_PATH) + + os.write(stdin, b'hello world!\n') + os.close(stdin) + out = os.read(stdout, 50) + os.close(stdout) + err = os.read(stderr, 50) + os.close(stderr) + GLib.spawn_close_pid(pid) + self.assertEqual(out, b'hello world!\n') + self.assertEqual(err, b'') + def test_spawn_async_envp(self): pid, stdin, stdout, stderr = GLib.spawn_async( ['sh', '-c', 'echo $TEST_VAR'], ['TEST_VAR=moo!'], @@ -142,5 +175,8 @@ class TestProcess(unittest.TestCase): self.assertEqual(out, b'moo!\n') def test_backwards_compat_flags(self): - self.assertEqual(GLib.SpawnFlags.DO_NOT_REAP_CHILD, - GLib.SPAWN_DO_NOT_REAP_CHILD) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', PyGIDeprecationWarning) + + self.assertEqual(GLib.SpawnFlags.DO_NOT_REAP_CHILD, + GLib.SPAWN_DO_NOT_REAP_CHILD) diff --git a/tests/test_thread.py b/tests/test_thread.py index 3d0557e..8c2d639 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -1,19 +1,23 @@ # -*- Mode: Python -*- import unittest -import testhelper from gi.repository import GLib +import testhelper + class TestThread(unittest.TestCase): def setUp(self): self.main = GLib.MainLoop() + self.called = False def from_thread_cb(self, test, enum): assert test == self.obj assert int(enum) == 0 - assert type(enum) != int + assert type(enum) is not int + self.called = True + GLib.idle_add(self.timeout_cb) def idle_cb(self): self.obj = testhelper.get_test_thread() @@ -22,8 +26,9 @@ class TestThread(unittest.TestCase): def test_extension_module(self): GLib.idle_add(self.idle_cb) - GLib.timeout_add(50, self.timeout_cb) + GLib.timeout_add(2000, self.timeout_cb) self.main.run() + self.assertTrue(self.called) def timeout_cb(self): self.main.quit() diff --git a/tests/test_typeclass.py b/tests/test_typeclass.py new file mode 100644 index 0000000..1f34a09 --- /dev/null +++ b/tests/test_typeclass.py @@ -0,0 +1,76 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# vim: tabstop=4 shiftwidth=4 expandtab +# +# test_typeclass.py: Tests for GTypeClass related methods and marshalling. +# +# Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + +import unittest + +from gi.repository import GObject +from gi.repository import GIMarshallingTests + + +class TestCoercion(unittest.TestCase): + def test_coerce_from_class(self): + prop = GObject.ObjectClass.find_property(GIMarshallingTests.PropertiesObject, + 'some-int') + + self.assertIsInstance(prop, GObject.GParamSpec) + self.assertEqual(prop.name, 'some-int') + self.assertEqual(prop.value_type, GObject.TYPE_INT) + self.assertEqual(prop.owner_type, + GIMarshallingTests.PropertiesObject.__gtype__) + + def test_coerce_from_gtype(self): + gtype = GIMarshallingTests.PropertiesObject.__gtype__ + prop = GObject.ObjectClass.find_property(gtype, 'some-int') + + self.assertIsInstance(prop, GObject.GParamSpec) + self.assertEqual(prop.name, 'some-int') + self.assertEqual(prop.value_type, GObject.TYPE_INT) + self.assertEqual(prop.owner_type, gtype) + + def test_coerce_from_instance(self): + obj = GIMarshallingTests.PropertiesObject() + prop = GObject.ObjectClass.find_property(obj, 'some-int') + + self.assertIsInstance(prop, GObject.GParamSpec) + self.assertEqual(prop.name, 'some-int') + self.assertEqual(prop.value_type, GObject.TYPE_INT) + self.assertEqual(prop.owner_type, obj.__gtype__) + + def test_marshalling_error(self): + with self.assertRaises(TypeError): + GObject.ObjectClass.find_property(object, 'some-int') + + with self.assertRaises(TypeError): + GObject.ObjectClass.find_property(42, 'some-int') + + +class TestTypeClassMethodsMovedToClass(unittest.TestCase): + def test_list_child_properties(self): + pspecs = GIMarshallingTests.PropertiesObject.list_properties() + pnames = [pspec.name for pspec in pspecs] + self.assertTrue('some-int' in pnames) + self.assertTrue('some-float' in pnames) + self.assertTrue('some-char' in pnames) + + def test_find_child_property(self): + pspec = GIMarshallingTests.PropertiesObject.find_property('some-int') + self.assertEqual(pspec.name, 'some-int') diff --git a/tests/test_unknown.py b/tests/test_unknown.py new file mode 100644 index 0000000..76fa185 --- /dev/null +++ b/tests/test_unknown.py @@ -0,0 +1,29 @@ +# -*- Mode: Python -*- + +import unittest + +from gi.repository import GObject + +import testhelper + + +TestInterface = GObject.GType.from_name('TestInterface') + + +class TestUnknown(unittest.TestCase): + def test_unknown_interface(self): + obj = testhelper.get_unknown() + TestUnknownGType = GObject.GType.from_name('TestUnknown') + TestUnknown = GObject.new(TestUnknownGType).__class__ + assert isinstance(obj, testhelper.Interface) + assert isinstance(obj, TestUnknown) + + def test_property(self): + obj = testhelper.get_unknown() + self.assertEqual(obj.get_property('some-property'), None) + obj.set_property('some-property', 'foo') + + def test_unknown_property(self): + obj = testhelper.get_unknown() + self.assertRaises(TypeError, obj.get_property, 'unknown') + self.assertRaises(TypeError, obj.set_property, 'unknown', '1') diff --git a/tests/testhelpermodule.c b/tests/testhelpermodule.c index 9b198c3..eb17af8 100644 --- a/tests/testhelpermodule.c +++ b/tests/testhelpermodule.c @@ -5,7 +5,12 @@ #include "test-unknown.h" #include "test-floating.h" -#include <pyglib-python-compat.h> +#define PYGI_DEFINE_TYPE(typename, symbol, csymbol) \ +PyTypeObject symbol = { \ + PyVarObject_HEAD_INIT(NULL, 0) \ + typename, \ + sizeof(csymbol) \ +}; static PyObject * _wrap_TestInterface__do_iface_method(PyObject *cls, PyObject *args, @@ -29,8 +34,8 @@ test_type_get_type(void) type_info = (GTypeInfo *)g_new0(GTypeInfo, 1); g_type_query(parent_type, &query); - type_info->class_size = query.class_size; - type_info->instance_size = query.instance_size; + type_info->class_size = (guint16)query.class_size; + type_info->instance_size = (guint16)query.instance_size; gtype = g_type_register_static(parent_type, "TestType", type_info, 0); @@ -82,7 +87,7 @@ _wrap_test_g_object_new (PyObject * self) PyObject *rv; obj = g_object_new(g_type_from_name("PyGObject"), NULL); - rv = PYGLIB_PyLong_FromLong(obj->ref_count); /* should be == 2 at this point */ + rv = PyLong_FromLong(obj->ref_count); /* should be == 2 at this point */ g_object_unref(obj); return rv; } @@ -111,7 +116,7 @@ static const PyMethodDef _PyTestInterface_methods[] = { }; /* TestInterface */ -PYGLIB_DEFINE_TYPE("test.Interface", PyTestInterface_Type, PyObject); +PYGI_DEFINE_TYPE("test.Interface", PyTestInterface_Type, PyObject); static PyObject * _wrap_TestInterface__do_iface_method(PyObject *cls, PyObject *args, PyObject *kwargs) @@ -136,7 +141,7 @@ _wrap_TestInterface__do_iface_method(PyObject *cls, PyObject *args, PyObject *kw return Py_None; } -PYGLIB_DEFINE_TYPE("testhelper.Unknown", PyTestUnknown_Type, PyGObject); +PYGI_DEFINE_TYPE("testhelper.Unknown", PyTestUnknown_Type, PyGObject); static void _wrap_TestInterface__proxy_do_iface_method(TestInterface *self) @@ -147,12 +152,12 @@ _wrap_TestInterface__proxy_do_iface_method(TestInterface *self) PyObject *py_args; PyObject *py_method; - __py_state = pyg_gil_state_ensure(); + __py_state = PyGILState_Ensure(); py_self = pygobject_new((GObject *) self); if (!py_self) { if (PyErr_Occurred()) PyErr_Print(); - pyg_gil_state_release(__py_state); + PyGILState_Release(__py_state); return; } py_args = PyTuple_New(0); @@ -162,7 +167,7 @@ _wrap_TestInterface__proxy_do_iface_method(TestInterface *self) PyErr_Print(); Py_DECREF(py_args); Py_DECREF(py_self); - pyg_gil_state_release(__py_state); + PyGILState_Release(__py_state); return; } py_retval = PyObject_CallObject(py_method, py_args); @@ -172,7 +177,7 @@ _wrap_TestInterface__proxy_do_iface_method(TestInterface *self) Py_DECREF(py_method); Py_DECREF(py_args); Py_DECREF(py_self); - pyg_gil_state_release(__py_state); + PyGILState_Release(__py_state); return; } if (py_retval != Py_None) { @@ -183,7 +188,7 @@ _wrap_TestInterface__proxy_do_iface_method(TestInterface *self) Py_DECREF(py_method); Py_DECREF(py_args); Py_DECREF(py_self); - pyg_gil_state_release(__py_state); + PyGILState_Release(__py_state); return; } @@ -191,7 +196,7 @@ _wrap_TestInterface__proxy_do_iface_method(TestInterface *self) Py_DECREF(py_method); Py_DECREF(py_args); Py_DECREF(py_self); - pyg_gil_state_release(__py_state); + PyGILState_Release(__py_state); } static void @@ -222,10 +227,10 @@ static const GInterfaceInfo __TestInterface__iinfo = { }; /* TestFloating */ -PYGLIB_DEFINE_TYPE("testhelper.Floating", PyTestFloating_Type, PyGObject); +PYGI_DEFINE_TYPE("testhelper.Floating", PyTestFloating_Type, PyGObject); /* TestOwnedByLibrary */ -PYGLIB_DEFINE_TYPE("testhelper.OwnedByLibrary", PyTestOwnedByLibrary_Type, PyGObject); +PYGI_DEFINE_TYPE("testhelper.OwnedByLibrary", PyTestOwnedByLibrary_Type, PyGObject); static PyObject * _wrap_test_owned_by_library_release (PyGObject *self) @@ -240,7 +245,7 @@ static const PyMethodDef _PyTestOwnedByLibrary_methods[] = { }; /* TestFloatingAndSunk */ -PYGLIB_DEFINE_TYPE("testhelper.FloatingAndSunk", PyTestFloatingAndSunk_Type, PyGObject); +PYGI_DEFINE_TYPE("testhelper.FloatingAndSunk", PyTestFloatingAndSunk_Type, PyGObject); static PyObject * _wrap_test_floating_and_sunk_release (PyGObject *self) @@ -357,11 +362,12 @@ test_paramspec_callback (GObject *object) static GValue * test_gvalue_callback (GObject *object, const GValue *v) { - GValue *ret = g_malloc0 (sizeof (GValue)); + GValue *ret; g_return_val_if_fail (G_IS_OBJECT (object), NULL); g_return_val_if_fail (G_IS_VALUE (v), NULL); + ret = g_malloc0 (sizeof (GValue)); g_value_init (ret, G_VALUE_TYPE (v)); g_value_copy (v, ret); return ret; @@ -370,10 +376,11 @@ test_gvalue_callback (GObject *object, const GValue *v) static GValue * test_gvalue_ret_callback (GObject *object, GType type) { - GValue *ret = g_malloc0 (sizeof (GValue)); + GValue *ret; g_return_val_if_fail (G_IS_OBJECT (object), NULL); + ret = g_malloc0 (sizeof (GValue)); g_value_init (ret, type); switch (type) { @@ -505,6 +512,17 @@ _wrap_test_value(PyObject *self, PyObject *args) } static PyObject * +_wrap_test_state_ensure_release(PyObject *self, PyObject *args) +{ + int state = pyg_gil_state_ensure (); + pyg_gil_state_release (state); + + Py_RETURN_NONE; +} + +#define PYGI_TYPE_VALUE_ARRAY (g_value_array_get_type()) + +static PyObject * _wrap_test_value_array(PyObject *self, PyObject *args) { GValue tvalue = {0,}, *value = &tvalue; @@ -513,7 +531,10 @@ _wrap_test_value_array(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "O", &obj)) return NULL; - g_value_init(value, G_TYPE_VALUE_ARRAY); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_value_init(value, PYGI_TYPE_VALUE_ARRAY); + G_GNUC_END_IGNORE_DEPRECATIONS + if (pyg_value_from_pyobject(value, obj)) { PyErr_SetString(PyExc_TypeError, "Could not convert to GValueArray"); return NULL; @@ -522,6 +543,56 @@ _wrap_test_value_array(PyObject *self, PyObject *args) return pyg_value_as_pyobject(value, FALSE); } + +static PyObject * +_wrap_value_array_get_nth_type(PyObject *self, PyObject *args) +{ + guint n; + GType type; + GValue *nth; + GValueArray *arr; + PyObject *obj; + + if (!PyArg_ParseTuple(args, "OI", &obj, &n)) + return NULL; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + + if (pyg_boxed_check(obj, G_TYPE_VALUE) && + G_VALUE_HOLDS(pyg_boxed_get(obj, GValue), PYGI_TYPE_VALUE_ARRAY)) { + arr = g_value_get_boxed(pyg_boxed_get(obj, GValue)); + } else if (pyg_boxed_check(obj, PYGI_TYPE_VALUE_ARRAY)) { + arr = pyg_boxed_get(obj, GValueArray); + } else { + PyErr_SetString(PyExc_TypeError, "First argument is not GValueArray"); + return NULL; + } + + if (n >= arr->n_values) { + PyErr_SetString(PyExc_TypeError, "Index is out of bounds"); + return NULL; + } + nth = g_value_array_get_nth(arr, n); + type = G_VALUE_TYPE(nth); + + G_GNUC_END_IGNORE_DEPRECATIONS + + return pyg_type_wrapper_new(type); +} + +static PyObject * +_wrap_constant_strip_prefix(PyObject *self, PyObject *args) +{ + const char *name, *strip_prefix; + const gchar *result; + + if (!PyArg_ParseTuple (args, "ss", &name, &strip_prefix)) + return NULL; + + result = pyg_constant_strip_prefix (name, strip_prefix); + return PyUnicode_FromString (result); +} + static PyObject * _wrap_test_gerror_exception(PyObject *self, PyObject *args) { @@ -593,32 +664,101 @@ _wrap_test_floating_and_sunk_get_instance_list (PyObject *self) return py_list; } +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + +static PyObject * +_wrap_test_parse_constructor_args (PyObject *self, PyObject *args) +{ + char *arg_names[] = {"label", NULL}; + char *prop_names[] = {"label", NULL}; + GParameter params[1] = {{0}}; + PyObject *parsed_args[1]; + guint nparams = 0; + + if (!PyArg_ParseTuple(args, "O", &(parsed_args[0]))) + return NULL; + + if (!pyg_parse_constructor_args ( + TYPE_TEST, arg_names, prop_names, params, &nparams, parsed_args)) { + return NULL; + } + + return PyLong_FromLong (nparams); +} + +G_GNUC_END_IGNORE_DEPRECATIONS + +static PyObject * +_wrap_test_to_unichar_conv (PyObject * self, PyObject *args) +{ + PyObject *obj; + gunichar result; + + if (!PyArg_ParseTuple(args, "O", &obj)) + return NULL; + + if (!pyg_pyobj_to_unichar_conv (obj, &result)) + return NULL; + + return PyLong_FromLong (result); +} + static PyMethodDef testhelper_functions[] = { + { "test_parse_constructor_args", (PyCFunction)_wrap_test_parse_constructor_args, METH_VARARGS }, { "get_test_thread", (PyCFunction)_wrap_get_test_thread, METH_NOARGS }, + { "test_to_unichar_conv", (PyCFunction)_wrap_test_to_unichar_conv, METH_VARARGS }, { "get_unknown", (PyCFunction)_wrap_get_unknown, METH_NOARGS }, { "create_test_type", (PyCFunction)_wrap_create_test_type, METH_NOARGS }, + { "test_state_ensure_release", (PyCFunction)_wrap_test_state_ensure_release, METH_NOARGS }, { "test_g_object_new", (PyCFunction)_wrap_test_g_object_new, METH_NOARGS }, { "connectcallbacks", (PyCFunction)_wrap_connectcallbacks, METH_VARARGS }, { "test_value", (PyCFunction)_wrap_test_value, METH_VARARGS }, { "test_value_array", (PyCFunction)_wrap_test_value_array, METH_VARARGS }, + { "value_array_get_nth_type", (PyCFunction)_wrap_value_array_get_nth_type, METH_VARARGS }, + { "constant_strip_prefix", (PyCFunction)_wrap_constant_strip_prefix, METH_VARARGS }, { "test_gerror_exception", (PyCFunction)_wrap_test_gerror_exception, METH_VARARGS }, { "owned_by_library_get_instance_list", (PyCFunction)_wrap_test_owned_by_library_get_instance_list, METH_NOARGS }, { "floating_and_sunk_get_instance_list", (PyCFunction)_wrap_test_floating_and_sunk_get_instance_list, METH_NOARGS }, { NULL, NULL } }; -PYGLIB_MODULE_START(testhelper, "testhelper") -{ +static struct PyModuleDef _testhelpermodule = { + PyModuleDef_HEAD_INIT, + "testhelper", + NULL, + -1, + testhelper_functions, + NULL, + NULL, + NULL, + NULL +}; + +#ifdef __GNUC__ +#define PYGI_MODINIT_FUNC __attribute__((visibility("default"))) PyMODINIT_FUNC +#else +#define PYGI_MODINIT_FUNC PyMODINIT_FUNC +#endif + +PYGI_MODINIT_FUNC PyInit_testhelper(void); + +PYGI_MODINIT_FUNC PyInit_testhelper(void) { + PyObject *module; + PyObject *gobject_module; PyObject *m, *d; - - pygobject_init(-1, -1, -1); + + module = PyModule_Create(&_testhelpermodule); + + if ((gobject_module = pygobject_init(-1, -1, -1)) == NULL) + return NULL; + Py_DECREF (gobject_module); d = PyModule_GetDict(module); if ((m = PyImport_ImportModule("gi.repository.GObject")) == NULL) { PyErr_SetString(PyExc_ImportError, "could not import gobject"); - return PYGLIB_MODULE_ERROR_RETURN; + return NULL; } /* TestInterface */ @@ -667,6 +807,7 @@ PYGLIB_MODULE_START(testhelper, "testhelper") &PyTestFloatingAndSunk_Type, Py_BuildValue("(O)", &PyGObject_Type)); + + return module; } -PYGLIB_MODULE_END diff --git a/tests/testmodule.py b/tests/testmodule.py deleted file mode 100644 index 3da8ed5..0000000 --- a/tests/testmodule.py +++ /dev/null @@ -1,22 +0,0 @@ -from gi.repository import GObject - - -class PyGObject(GObject.GObject): - __gtype_name__ = 'PyGObject' - __gproperties__ = { - 'label': (GObject.TYPE_STRING, - 'label property', - 'the label of the object', - 'default', GObject.PARAM_READWRITE), - } - - def __init__(self): - self._props = {} - GObject.GObject.__init__(self) - self.set_property('label', 'hello') - - def do_set_property(self, name, value): - self._props[name] = value - - def do_get_property(self, name): - return self._props[name] diff --git a/tests/valgrind.supp b/tests/valgrind.supp new file mode 100644 index 0000000..5792a7c --- /dev/null +++ b/tests/valgrind.supp @@ -0,0 +1,30 @@ +# https://bugzilla.redhat.com/show_bug.cgi?id=1538073 + +{ + <py36-start1> + Memcheck:Cond + fun:__wcsnlen_sse4_1 + fun:wcsrtombs + fun:wcstombs + fun:wcstombs + fun:encode_current_locale* +} + +{ + <fontconfig> + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:FcPatternObjectInsertElt + fun:FcPatternObjectAddWithBinding +} + +{ + <fontconfig-2> + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:FcPatternCreate + fun:FcParsePattern + fun:FcEndElement +} |