[geary/mjog/unit-test-subproject: 52/54] Move generic unit test classes to a new basically-standalone subproject
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/unit-test-subproject: 52/54] Move generic unit test classes to a new basically-standalone subproject
- Date: Tue, 30 Jun 2020 04:49:08 +0000 (UTC)
commit f4b5f12a55fef2de316abd27eaa8d7fcfaeb223d
Author: Michael Gratton <mike vee net>
Date: Fri May 8 18:30:35 2020 +1000
Move generic unit test classes to a new basically-standalone subproject
Break out the generic testing code into something easily re-used, and
improve the API substantially:
* Use generics to reduce the number of equality tests to effectively
a single one
* Make all assert args consistent in that the actual value is always
listed first.
* Add convenience API for common string/array/collection assertions
meson.build | 9 +
subprojects/vala-unit/COPYING | 464 ++++++++++++++++++
subprojects/vala-unit/README.md | 6 +
subprojects/vala-unit/meson.build | 152 ++++++
subprojects/vala-unit/meson_options.txt | 12 +
subprojects/vala-unit/src/async-result-waiter.vala | 108 +++++
.../vala-unit/src/collection-assertions.vala | 499 +++++++++++++++++++
subprojects/vala-unit/src/expected-call.vala | 148 ++++++
subprojects/vala-unit/src/mock-object.vala | 318 ++++++++++++
subprojects/vala-unit/src/test-adaptor.vala | 75 +++
subprojects/vala-unit/src/test-assertions.vala | 533 +++++++++++++++++++++
subprojects/vala-unit/src/test-case.vala | 207 ++++++++
.../vala-unit/test/collection-assertions.vala | 216 +++++++++
subprojects/vala-unit/test/test-assertions.vala | 299 ++++++++++++
subprojects/vala-unit/test/test-driver.vala | 26 +
test/meson.build | 32 +-
test/mock-object.vala | 442 -----------------
test/test-case.vala | 462 ------------------
18 files changed, 3078 insertions(+), 930 deletions(-)
---
diff --git a/meson.build b/meson.build
index 3d7f77cff..61d0e0963 100644
--- a/meson.build
+++ b/meson.build
@@ -153,6 +153,15 @@ endif
# Build glue
#
+vala_unit_proj = subproject(
+ 'vala-unit',
+ default_options: [
+ 'install=false',
+ 'valadoc=@0@'.format(enable_valadoc)
+ ]
+)
+vala_unit_dep = vala_unit_proj.get_variable('vala_unit_dep')
+
if enable_valadoc
valadoc = find_program('valadoc')
endif
diff --git a/subprojects/vala-unit/COPYING b/subprojects/vala-unit/COPYING
new file mode 100644
index 000000000..74e18dbd2
--- /dev/null
+++ b/subprojects/vala-unit/COPYING
@@ -0,0 +1,464 @@
+This program 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.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/subprojects/vala-unit/README.md b/subprojects/vala-unit/README.md
new file mode 100644
index 000000000..aeb012d10
--- /dev/null
+++ b/subprojects/vala-unit/README.md
@@ -0,0 +1,6 @@
+
+ValaUnit
+========
+
+A Glib-based, xUnit-style unit testing and mock object framework for
+Vala projects.
diff --git a/subprojects/vala-unit/meson.build b/subprojects/vala-unit/meson.build
new file mode 100644
index 000000000..eb1975b3e
--- /dev/null
+++ b/subprojects/vala-unit/meson.build
@@ -0,0 +1,152 @@
+project(
+ 'vala-unit',
+ [ 'vala', 'c' ],
+ version: '1.0',
+ license: 'LGPL2.1+',
+ meson_version: '>= 0.50',
+)
+
+enable_install = get_option('install')
+enable_valadoc = get_option('valadoc')
+
+add_project_arguments(
+ [
+ '--abi-stability',
+ '--enable-checking',
+ '--enable-experimental-non-null',
+ '--fatal-warnings',
+ '--nostdpkg'
+ ],
+ language: 'vala'
+)
+
+target_vala = '0.44'
+target_glib = '2.62'
+
+if not meson.get_compiler('vala').version().version_compare('>=' + target_vala)
+ error('Vala does not meet minimum required version: ' + target_vala)
+endif
+
+gee = dependency('gee-0.8')
+gio = dependency('gio-2.0')
+glib = dependency('glib-2.0', version: '>=' + target_glib)
+gobject = dependency('gobject-2.0')
+
+g_ir_compiler = find_program('g-ir-compiler')
+if enable_valadoc
+ valadoc = find_program('valadoc')
+endif
+
+dependencies = [
+ gee,
+ gio,
+ glib,
+ gobject
+]
+
+lib_sources = files(
+ 'src/async-result-waiter.vala',
+ 'src/collection-assertions.vala',
+ 'src/expected-call.vala',
+ 'src/mock-object.vala',
+ 'src/test-adaptor.vala',
+ 'src/test-assertions.vala',
+ 'src/test-case.vala',
+)
+
+test_sources = files(
+ 'test/collection-assertions.vala',
+ 'test/test-assertions.vala',
+ 'test/test-driver.vala',
+)
+
+package_name = 'ValaUnit'
+package_version = '1.0'
+package_full = '@0@-@1@'.format(package_name, package_version)
+package_vapi = '@0@-@1@'.format(meson.project_name(), package_version)
+package_gir = package_full + '.gir'
+
+vala_unit_lib = library(
+ meson.project_name(),
+ lib_sources,
+ dependencies: dependencies,
+ # Ensure we always get debug symbols.
+ override_options : [
+ 'debug=true',
+ 'strip=false',
+ ],
+ vala_vapi: package_vapi + '.vapi',
+ vala_gir: package_gir,
+ install: enable_install,
+ install_dir: [true, true, true, true]
+)
+
+vala_unit_dep = declare_dependency(
+ link_with : vala_unit_lib,
+ include_directories: include_directories('.')
+)
+
+custom_target(
+ meson.project_name() + '-typelib',
+ command: [
+ g_ir_compiler,
+ '--output', '@OUTPUT@',
+ meson.current_build_dir() / package_gir,
+ ],
+ output: [package_full + '.typelib'],
+ depends: vala_unit_lib,
+ install: enable_install,
+ install_dir: get_option('libdir') / 'girepository-1.0'
+)
+
+if enable_valadoc
+ # Hopefully Meson will get baked-in valadoc support, so we don't have
+ # to do this any more. https://github.com/mesonbuild/meson/issues/894
+ valadoc_dep_args = []
+ foreach dep : dependencies
+ valadoc_dep_args += '--pkg'
+ valadoc_dep_args += dep.name()
+ endforeach
+
+ docs = custom_target(
+ 'valadoc',
+ build_by_default: true,
+ depends: [vala_unit_lib],
+ input: lib_sources,
+ output: 'valadoc',
+ command: [
+ valadoc,
+ '--verbose',
+ '--force',
+ '--fatal-warnings',
+ '--package-name=@0@'.format(package_vapi),
+ '--package-version=@0@'.format(meson.project_version()),
+ '-b', meson.current_source_dir(),
+ '-o', '@OUTPUT@',
+ '@INPUT@',
+ ] + valadoc_dep_args
+ )
+
+ if enable_install
+ install_subdir(
+ meson.current_build_dir() / 'valadoc',
+ install_dir: get_option('datadir') / 'doc' / 'vala-unit' / 'valadoc'
+ )
+ endif
+endif
+
+test_driver = executable(
+ 'test-driver',
+ test_sources,
+ dependencies: dependencies + [ vala_unit_dep ],
+ # Always do a plain debug build to avoid compiler optimsations that
+ # might render testing invalid, and to ensure we get debug symbols.
+ # Ensure we always get debug symbols.
+ override_options : [
+ 'debug=true',
+ 'optimization=0',
+ 'strip=false',
+ ],
+)
+
+test('tests', test_driver)
diff --git a/subprojects/vala-unit/meson_options.txt b/subprojects/vala-unit/meson_options.txt
new file mode 100644
index 000000000..e4a8a24da
--- /dev/null
+++ b/subprojects/vala-unit/meson_options.txt
@@ -0,0 +1,12 @@
+option(
+ 'install',
+ type: 'boolean',
+ value: true,
+ description: 'Whether to install the library\'s files'
+)
+option(
+ 'valadoc',
+ type: 'boolean',
+ value: true,
+ description: 'Whether to build the documentaton (requires valadoc).'
+)
diff --git a/subprojects/vala-unit/src/async-result-waiter.vala
b/subprojects/vala-unit/src/async-result-waiter.vala
new file mode 100644
index 000000000..02025d970
--- /dev/null
+++ b/subprojects/vala-unit/src/async-result-waiter.vala
@@ -0,0 +1,108 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+
+/**
+ * Allows non-async code to wait for async calls to be completed.
+ *
+ * To use instances of this class, call an async function or method
+ * using the `begin()` form, passing {@link async_completion} as
+ * completion argument (that is, the last argument):
+ *
+ * {{{
+ * var waiter = new AsyncResultWaiter();
+ * my_async_call.begin("foo", waiter.async_completion);
+ * }}}
+ *
+ * Then, when you want to ensure the call is complete, pass the result
+ * of calling {@link async_result} to its `end()` form:
+ *
+ * {{{
+ * my_async_call.end(waiter.async_result());
+ * }}}
+ *
+ * This will block until the async call has completed.
+ *
+ * Note that {@link TestCase} exposes the same interface, so it is
+ * usually easier to just call those when testing a single async call,
+ * or multiple, non-interleaved async calls.
+ *
+ * This class is implemented as a FIFO queue of {@link
+ * GLib.AsyncResult} instances, and thus can be used for waiting for
+ * multiple calls. Note however the ordering depends on the order in
+ * which the async calls being invoked are executed and are
+ * completed. Thus if testing multiple interleaved async calls, you
+ * should probably use an instance of this class per call.
+ */
+public class ValaUnit.AsyncResultWaiter : GLib.Object {
+
+
+ /** The main loop that is executed when waiting for async results. */
+ public GLib.MainContext main_loop { get; construct set; }
+
+ private GLib.AsyncQueue<GLib.AsyncResult> results =
+ new GLib.AsyncQueue<GLib.AsyncResult>();
+
+
+ /**
+ * Constructs a new waiter.
+ *
+ * @param main_loop a main loop context to execute when waiting
+ * for an async result
+ */
+ public AsyncResultWaiter(GLib.MainContext main_loop) {
+ Object(main_loop: main_loop);
+ }
+
+ /**
+ * The last argument of an async call to be tested.
+ *
+ * Records the given {@link GLib.AsyncResult}, adding it to the
+ * internal FIFO queue. This method should be called as the
+ * completion of an async call to be tested.
+ *
+ * To use it, pass as the last argument to the `begin()` form of
+ * the async call:
+ *
+ * {{{
+ * var waiter = new AsyncResultWaiter();
+ * my_async_call.begin("foo", waiter.async_completion);
+ * }}}
+ */
+ public void async_completion(GLib.Object? object,
+ GLib.AsyncResult result) {
+ this.results.push(result);
+ // Notify the loop so that if async_result() has already been
+ // called, that method won't block.
+ this.main_loop.wakeup();
+ }
+
+ /**
+ * Waits for async calls to complete, returning the most recent one.
+ *
+ * This returns the first {@link GLib.AsyncResult} from the
+ * internal FIFO queue that has been provided by {@link
+ * async_completion}. If none are available, it will pump the main
+ * loop, blocking until one becomes available.
+ *
+ * To use it, pass its return value as the argument to the `end()`
+ * call:
+ *
+ * {{{
+ * my_async_call.end(waiter.async_result());
+ * }}}
+ */
+ public GLib.AsyncResult async_result() {
+ GLib.AsyncResult? result = this.results.try_pop();
+ while (result == null) {
+ this.main_loop.iteration(true);
+ result = this.results.try_pop();
+ }
+ return (GLib.AsyncResult) result;
+ }
+
+}
diff --git a/subprojects/vala-unit/src/collection-assertions.vala
b/subprojects/vala-unit/src/collection-assertions.vala
new file mode 100644
index 000000000..3b34acda1
--- /dev/null
+++ b/subprojects/vala-unit/src/collection-assertions.vala
@@ -0,0 +1,499 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Defines default test assertions for specific strings, arrays and collections.
+ *
+ * Call {@link TestAssertions.assert_string}, {@link
+ * TestAssertions.assert_array} and {@link
+ * TestAssertions.assert_collection} methods, accessible from
+ * subclasses of {@link TestCase} to construct these objects.
+ */
+public interface ValaUnit.CollectionAssertions<E> : GLib.Object {
+
+
+ /**
+ * Asserts the collection is empty.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public abstract CollectionAssertions<E> is_empty()
+ throws GLib.Error;
+
+ /**
+ * Asserts the collection is non-empty.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public abstract CollectionAssertions<E> is_non_empty()
+ throws GLib.Error;
+
+ /**
+ * Asserts the collection has an expected length.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public abstract CollectionAssertions<E> size(uint32 expected)
+ throws GLib.Error;
+
+ /**
+ * Asserts the collection contains an expected element.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public abstract CollectionAssertions<E> contains(E expected)
+ throws GLib.Error;
+
+ /**
+ * Asserts the collection does not contain an expected element.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public abstract CollectionAssertions<E> not_contains(E expected)
+ throws GLib.Error;
+
+ /**
+ * Asserts the collection's first element is as expected.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public CollectionAssertions<E> first_is(E expected)
+ throws GLib.Error {
+ at_index_is(0, expected);
+ return this;
+ }
+
+ /**
+ * Asserts the collection's nth element is as expected.
+ *
+ * Note the give position is is 1-based, not 0-based.
+ *
+ * Returns the same object to allow assertion chaining.
+ */
+ public abstract CollectionAssertions<E> at_index_is(uint32 position,
+ E expected)
+ throws GLib.Error;
+
+
+}
+
+internal class ValaUnit.StringCollectionAssertion : GLib.Object,
+ CollectionAssertions<string> {
+
+
+ private string actual;
+ private string? context;
+
+
+ internal StringCollectionAssertion(string actual, string? context) {
+ this.actual = actual;
+ this.context = context;
+ }
+
+ public CollectionAssertions<string> is_empty() throws GLib.Error {
+ if (this.actual.length != 0) {
+ ValaUnit.assert(
+ "“%s”.length = %u, expected empty".printf(
+ this.actual,
+ this.actual.length
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<string> is_non_empty()
+ throws GLib.Error {
+ if (this.actual.length == 0) {
+ ValaUnit.assert(
+ "string is empty, expected non-empty", this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<string> size(uint32 expected)
+ throws GLib.Error {
+ if (this.actual.length != expected) {
+ ValaUnit.assert(
+ "“%s”.length = %u, expected %u".printf(
+ this.actual,
+ this.actual.length,
+ expected
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<string> contains(string expected)
+ throws GLib.Error {
+ if (!(expected in this.actual)) {
+ ValaUnit.assert(
+ "“%s” does not contain “%s”".printf(
+ this.actual,
+ expected
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<string> not_contains(string expected)
+ throws GLib.Error {
+ if (expected in this.actual) {
+ ValaUnit.assert(
+ "“%s” should not contain “%s”".printf(
+ this.actual,
+ expected
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<string> at_index_is(uint32 index,
+ string expected)
+ throws GLib.Error {
+ if (this.actual.index_of(expected) != index) {
+ ValaUnit.assert(
+ "“%s”[%u:%u] != “%s”".printf(
+ this.actual,
+ index,
+ index + (uint) expected.length,
+ expected
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+}
+
+
+internal class ValaUnit.ArrayCollectionAssertion<E> : GLib.Object,
+ CollectionAssertions<E> {
+
+
+ private E[] actual;
+ private string? context;
+
+
+ internal ArrayCollectionAssertion(E[] actual, string? context)
+ throws TestError {
+ this.actual = actual;
+ this.context = context;
+
+ GLib.Type UNSUPPORTED[] = {
+ typeof(bool),
+ typeof(char),
+ typeof(short),
+ typeof(int),
+ typeof(int64),
+ typeof(uchar),
+ typeof(ushort),
+ typeof(uint),
+ typeof(uint64),
+ typeof(float),
+ typeof(double)
+ };
+ var type = typeof(E);
+ if (type.is_enum() || type in UNSUPPORTED) {
+ throw new TestError.UNSUPPORTED(
+ "Arrays containing non-pointer values not currently supported. See GNOME/vala#964"
+ );
+ }
+ }
+
+ public CollectionAssertions<E> is_empty() throws GLib.Error {
+ if (this.actual.length != 0) {
+ ValaUnit.assert(
+ "%s is not empty".printf(
+ to_collection_display()
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> is_non_empty()
+ throws GLib.Error {
+ if (this.actual.length == 0) {
+ ValaUnit.assert(
+ "%s is empty, expected non-empty".printf(
+ to_collection_display()
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> size(uint32 expected)
+ throws GLib.Error {
+ if (this.actual.length != expected) {
+ ValaUnit.assert(
+ "%s.length == %d, expected %u".printf(
+ to_collection_display(),
+ this.actual.length,
+ expected
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> contains(E expected)
+ throws GLib.Error {
+ E boxed_expected = box_value(expected);
+ bool found = false;
+ for (int i = 0; i < this.actual.length; i++) {
+ try {
+ assert_equal(box_value(this.actual[i]), boxed_expected);
+ found = true;
+ break;
+ } catch (TestError.FAILED err) {
+ // no-op
+ }
+ }
+ if (!found) {
+ ValaUnit.assert(
+ "%s does not contain %s".printf(
+ to_collection_display(),
+ to_display_string(boxed_expected)
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> not_contains(E expected)
+ throws GLib.Error {
+ E boxed_expected = box_value(expected);
+ for (int i = 0; i < this.actual.length; i++) {
+ try {
+ assert_equal(box_value(this.actual[i]), boxed_expected);
+ ValaUnit.assert(
+ "%s does not contain %s".printf(
+ to_collection_display(),
+ to_display_string(boxed_expected)
+ ),
+ this.context
+ );
+ break;
+ } catch (TestError.FAILED err) {
+ // no-op
+ }
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> at_index_is(uint32 index, E expected)
+ throws GLib.Error {
+ if (index >= this.actual.length) {
+ ValaUnit.assert(
+ "%s.length == %u, expected >= %u".printf(
+ to_collection_display(),
+ this.actual.length,
+ index
+ ),
+ this.context
+ );
+ }
+ E boxed_actual = box_value(this.actual[index]);
+ E boxed_expected = box_value(expected);
+ try {
+ assert_equal(boxed_actual, boxed_expected);
+ } catch (TestError.FAILED err) {
+ ValaUnit.assert(
+ "%s[%u] == %s, expected: %s".printf(
+ to_collection_display(),
+ index,
+ to_display_string(boxed_actual),
+ to_display_string(boxed_expected)
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ private string to_collection_display() {
+ var buf = new GLib.StringBuilder();
+ int len = this.actual.length;
+ buf.append(typeof(E).name());
+ buf.append("[]");
+
+ if (len > 0) {
+ buf.append_c('{');
+ buf.append(to_display_string(box_value(this.actual[0])));
+
+ if (len == 2) {
+ buf.append_c(',');
+ buf.append(to_display_string(box_value(this.actual[1])));
+ } else if (len > 2) {
+ buf.append(", … (%d more)".printf(len - 2));
+ }
+ buf.append_c('}');
+ }
+ return buf.str;
+ }
+
+}
+
+
+internal class ValaUnit.GeeCollectionAssertion<E> :
+ GLib.Object,
+ CollectionAssertions<E> {
+
+
+ private Gee.Collection<E> actual;
+ private string? context;
+
+
+ internal GeeCollectionAssertion(Gee.Collection<E> actual, string? context) {
+ this.actual = actual;
+ this.context = context;
+ }
+
+ public CollectionAssertions<E> is_empty() throws GLib.Error {
+ if (!this.actual.is_empty) {
+ ValaUnit.assert(
+ "%s.length = %d, expected empty".printf(
+ to_collection_display(),
+ this.actual.size
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> is_non_empty()
+ throws GLib.Error {
+ if (this.actual.is_empty) {
+ ValaUnit.assert(
+ "%s is empty, expected non-empty".printf(
+ to_collection_display()
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> size(uint32 expected)
+ throws GLib.Error {
+ if (this.actual.size != expected) {
+ ValaUnit.assert(
+ "%s.size == %d, expected %u".printf(
+ to_collection_display(),
+ this.actual.size,
+ expected
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> contains(E expected)
+ throws GLib.Error {
+ if (!(expected in this.actual)) {
+ ValaUnit.assert(
+ "%s does not contain %s".printf(
+ to_collection_display(),
+ to_display_string(box_value(expected))
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> not_contains(E expected)
+ throws GLib.Error {
+ if (expected in this.actual) {
+ ValaUnit.assert(
+ "%s should not contain %s".printf(
+ to_collection_display(),
+ to_display_string(box_value(expected))
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ public CollectionAssertions<E> at_index_is(uint32 index, E expected)
+ throws GLib.Error {
+ if (index >= this.actual.size) {
+ ValaUnit.assert(
+ "%s.length == %d, expected >= %u".printf(
+ to_collection_display(),
+ this.actual.size,
+ index
+ ),
+ this.context
+ );
+ }
+ Gee.Iterator<E> iterator = this.actual.iterator();
+ for (int i = 0; i <= index; i++) {
+ iterator.next();
+ }
+ E boxed_actual = box_value(iterator.get());
+ E boxed_expected = box_value(expected);
+ try {
+ assert_equal(boxed_actual, boxed_expected);
+ } catch (TestError.FAILED err) {
+ ValaUnit.assert(
+ "%s[%u] == %s, expected: %s".printf(
+ to_collection_display(),
+ index,
+ to_display_string(boxed_actual),
+ to_display_string(boxed_expected)
+ ),
+ this.context
+ );
+ }
+ return this;
+ }
+
+ private string to_collection_display() {
+ var buf = new GLib.StringBuilder();
+ int len = this.actual.size;
+ buf.append("Gee.Collection<");
+ buf.append(typeof(E).name());
+ buf.append_c('>');
+
+ if (len > 0) {
+ Gee.Iterator<E> iterator = this.actual.iterator();
+ iterator.next();
+ buf.append_c('{');
+ buf.append(to_display_string(box_value(iterator.get())));
+
+ if (len == 2) {
+ iterator.next();
+ buf.append_c(',');
+ buf.append(to_display_string(box_value(iterator.get())));
+ } else if (len > 2) {
+ buf.append(", … (%d more)".printf(len - 2));
+ }
+ buf.append_c('}');
+ }
+ return buf.str;
+ }
+
+}
diff --git a/subprojects/vala-unit/src/expected-call.vala b/subprojects/vala-unit/src/expected-call.vala
new file mode 100644
index 000000000..1230624c9
--- /dev/null
+++ b/subprojects/vala-unit/src/expected-call.vala
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018-2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Represents an expected method call on a mock object.
+ *
+ * An instance of this object is returned when calling {@link
+ * MockObject.expect_call}, and may be used to further specify
+ * expectations, such that the mock method should throw a specific
+ * error or return a specific value or object.
+ */
+public class ValaUnit.ExpectedCall : GLib.Object {
+
+
+ /** Options for handling async calls. */
+ public enum AsyncCallOptions {
+
+ /** Check and return from the expected call immediately. */
+ CONTINUE,
+
+ /**
+ * Check and return from the expected call when idle.
+ *
+ * This will yield when the call is made, being resuming when
+ * idle.
+ */
+ CONTINUE_AT_IDLE,
+
+ /**
+ * Check and return from the expected call when requested.
+ *
+ * This will yield when the call is made, resuming when {@link
+ * ExpectedCall.async_resume} is called.
+ */
+ PAUSE;
+
+ }
+
+
+ /** The name of the expected call. */
+ public string name { get; private set; }
+
+ /** Determines how async calls are handled. */
+ public AsyncCallOptions async_behaviour {
+ get; private set; default = CONTINUE;
+ }
+
+ /** The error to be thrown by the call, if any. */
+ public GLib.Error? throw_error { get; private set; default = null; }
+
+ /** An object to be returned by the call, if any. */
+ public GLib.Object? return_object { get; private set; default = null; }
+
+ /** A value to be returned by the call, if any. */
+ public GLib.Variant? return_value { get; private set; default = null; }
+
+ /** Determines if the call has been made or not. */
+ public bool was_called { get; private set; default = false; }
+
+ /** Determines if an async call has been resumed or not. */
+ public bool async_resumed { get; private set; default = false; }
+
+ // XXX Arrays can't be GObject properties :(
+ internal GLib.Object?[]? expected_args = null;
+ private GLib.Object?[]? called_args = null;
+
+ internal unowned GLib.SourceFunc? async_callback = null;
+
+
+ internal ExpectedCall(string name, GLib.Object?[]? args) {
+ this.name = name;
+ this.expected_args = args;
+ }
+
+ /** Sets the behaviour for an async call. */
+ public ExpectedCall async_call(AsyncCallOptions behaviour) {
+ this.async_behaviour = behaviour;
+ return this;
+ }
+
+ /** Sets an object that the call should return. */
+ public ExpectedCall returns_object(GLib.Object value) {
+ this.return_object = value;
+ return this;
+ }
+
+ /** Sets a bool value that the call should return. */
+ public ExpectedCall returns_boolean(bool value) {
+ this.return_value = new GLib.Variant.boolean(value);
+ return this;
+ }
+
+ /** Sets an error that the cal should throw. */
+ public ExpectedCall @throws(GLib.Error err) {
+ this.throw_error = err;
+ return this;
+ }
+
+ /**
+ * Resumes an async call that has been paused.
+ *
+ * Throws an assertion error if the call has not yet been called
+ * or has not been paused.
+ */
+ public void async_resume() throws TestError {
+ if (this.async_callback == null) {
+ throw new TestError.FAILED(
+ "Async call not called, could not resume"
+ );
+ }
+ if (this.async_resumed) {
+ throw new TestError.FAILED(
+ "Async call already resumed"
+ );
+ }
+ this.async_resumed = true;
+ this.async_callback();
+ }
+
+ /** Determines if an argument was given in the specific position. */
+ public T called_arg<T>(int pos) throws TestError {
+ if (this.called_args == null || this.called_args.length < (pos + 1)) {
+ throw new TestError.FAILED(
+ "%s call argument %u, type %s, not present".printf(
+ this.name, pos, typeof(T).name()
+ )
+ );
+ }
+ if (!(this.called_args[pos] is T)) {
+ throw new TestError.FAILED(
+ "%s call argument %u not of type %s".printf(
+ this.name, pos, typeof(T).name()
+ )
+ );
+ }
+ return (T) this.called_args[pos];
+ }
+
+ internal void called(GLib.Object?[]? args) {
+ this.was_called = true;
+ this.called_args = args;
+ }
+
+}
diff --git a/subprojects/vala-unit/src/mock-object.vala b/subprojects/vala-unit/src/mock-object.vala
new file mode 100644
index 000000000..766777a43
--- /dev/null
+++ b/subprojects/vala-unit/src/mock-object.vala
@@ -0,0 +1,318 @@
+/*
+ * Copyright © 2018-2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Denotes a dummy object that can be injected into code being tested.
+ *
+ * Mock objects are unit testing fixtures that are used to provide
+ * instances of specific classes or interfaces which are required by
+ * the code being tested. For example, if an object being tested
+ * requires certain objects to be passed in via its constructor or as
+ * arguments of method calls and uses these to implement its
+ * behaviour, mock objects that fulfill these requirements can be used.
+ *
+ * Mock objects provide a means of both ensuring code being tested
+ * makes expected method calls with expected arguments on its
+ * dependencies, and a means of orchestrating the return value and
+ * exceptions raised when these methods are called, if any.
+ *
+ * To specify a specific method should be called on a mock object,
+ * call {@link expect_call} with the name of the method and optionally
+ * the arguments that are expected. The returned {@link ExpectedCall}
+ * object can be used to specify any exception or return values for
+ * the method. After executing the code being tested, call {@link
+ * assert_expectations} to ensure that the actual calls made matched
+ * those expected.
+ */
+public interface ValaUnit.MockObject : GLib.Object, TestAssertions {
+
+
+ public static GLib.Object box_arg<T>(T value) {
+ return new BoxArgument<T>(value);
+ }
+
+ public static GLib.Object int_arg(int value) {
+ return new IntArgument(value);
+ }
+
+ public static GLib.Object uint_arg(uint value) {
+ return new UintArgument(value);
+ }
+
+ protected abstract Gee.Queue<ExpectedCall> expected { get; set; }
+
+
+ public ExpectedCall expect_call(string name, GLib.Object?[]? args = null) {
+ ExpectedCall expected = new ExpectedCall(name, args);
+ this.expected.offer(expected);
+ return expected;
+ }
+
+ public void assert_expectations() throws GLib.Error {
+ assert_true(this.expected.is_empty,
+ "%d expected calls not made".printf(this.expected.size));
+ reset_expectations();
+ }
+
+ public void reset_expectations() {
+ this.expected.clear();
+ }
+
+ protected bool boolean_call(string name,
+ GLib.Object?[] args,
+ bool default_return)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ return check_boolean_call(expected, default_return);
+ }
+
+ protected async bool boolean_call_async(string name,
+ GLib.Object?[] args,
+ bool default_return)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ if (async_call_yield(expected, this.boolean_call_async.callback)) {
+ yield;
+ }
+ return check_boolean_call(expected, default_return);
+ }
+
+ protected R object_call<R>(string name,
+ GLib.Object?[] args,
+ R default_return)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ return check_object_call(expected, default_return);
+ }
+
+ protected async R object_call_async<R>(string name,
+ GLib.Object?[] args,
+ R default_return)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ if (async_call_yield(expected, this.object_call_async.callback)) {
+ yield;
+ }
+ return check_object_call(expected, default_return);
+ }
+
+ protected R object_or_throw_call<R>(string name,
+ GLib.Object?[] args,
+ GLib.Error default_error)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ return check_object_or_throw_call(expected, default_error);
+ }
+
+ protected async R object_or_throw_call_async<R>(string name,
+ GLib.Object?[] args,
+ GLib.Error default_error)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ if (async_call_yield(expected, this.object_or_throw_call_async.callback)) {
+ yield;
+ }
+ return check_object_or_throw_call(expected, default_error);
+ }
+
+ protected void void_call(string name, GLib.Object?[] args)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ check_for_exception(expected);
+ }
+
+ protected async void void_call_async(string name, GLib.Object?[] args)
+ throws GLib.Error {
+ ExpectedCall expected = call_made(name, args);
+ if (async_call_yield(expected, this.void_call_async.callback)) {
+ yield;
+ }
+ check_for_exception(expected);
+ }
+
+ private ExpectedCall call_made(string name, GLib.Object?[] args)
+ throws GLib.Error {
+ assert_false(this.expected.is_empty, "Unexpected call: %s".printf(name));
+
+ ExpectedCall expected = this.expected.poll();
+ assert_equal(name, expected.name, "Unexpected call");
+ if (expected.expected_args != null) {
+ assert_args(args, expected.expected_args, "Call %s".printf(name));
+ }
+
+ expected.called(args);
+ return expected;
+ }
+
+ private void assert_args(GLib.Object?[] actual_args,
+ GLib.Object?[] expected_args,
+ string context)
+ throws GLib.Error {
+ int args = 0;
+ foreach (var expected in expected_args) {
+ if (args >= actual_args.length) {
+ break;
+ }
+
+ GLib.Object? actual = actual_args[args];
+ string arg_context = "%s, argument #%d".printf(context, args++);
+
+ if (expected is Argument) {
+ assert_non_null(actual, arg_context);
+ ((Argument) expected).assert((GLib.Object) actual, arg_context);
+ } else if (expected != null) {
+ var non_null_expected = (GLib.Object) expected;
+
+ assert_non_null(actual, arg_context);
+ var non_null_actual = (GLib.Object) actual;
+
+ assert_equal(
+ non_null_expected.get_type(), non_null_actual.get_type(),
+ arg_context
+ );
+ assert_equal(
+ non_null_actual,
+ non_null_expected,
+ arg_context
+ );
+ } else {
+ assert_null(actual, arg_context);
+
+ }
+ }
+
+ assert_equal(
+ actual_args.length,
+ expected_args.length,
+ "%s: argument list length".printf(context)
+ );
+ }
+
+ private bool async_call_yield(ExpectedCall expected,
+ GLib.SourceFunc @callback) {
+ var @yield = false;
+ if (expected.async_behaviour != CONTINUE) {
+ expected.async_callback = @callback;
+ if (expected.async_behaviour == CONTINUE_AT_IDLE) {
+ GLib.Idle.add(() => {
+ try {
+ expected.async_resume();
+ } catch (GLib.Error err) {
+ critical(
+ "Async call already resumed: %s", err.message
+ );
+ }
+ return GLib.Source.REMOVE;
+ });
+ }
+ @yield = true;
+ }
+ return @yield;
+ }
+
+ private inline bool check_boolean_call(ExpectedCall expected,
+ bool default_return)
+ throws GLib.Error {
+ check_for_exception(expected);
+ bool return_value = default_return;
+ if (expected.return_value != null) {
+ return_value = ((GLib.Variant) expected.return_value).get_boolean();
+ }
+ return return_value;
+ }
+
+ private inline R check_object_call<R>(ExpectedCall expected,
+ R default_return)
+ throws GLib.Error {
+ check_for_exception(expected);
+ R? return_object = default_return;
+ if (expected.return_object != null) {
+ return_object = (R) expected.return_object;
+ }
+ return return_object;
+ }
+
+ private inline R check_object_or_throw_call<R>(ExpectedCall expected,
+ GLib.Error default_error)
+ throws GLib.Error {
+ check_for_exception(expected);
+ if (expected.return_object == null) {
+ throw default_error;
+ }
+ return expected.return_object;
+ }
+
+ private inline void check_for_exception(ExpectedCall expected)
+ throws GLib.Error {
+ if (expected.throw_error != null) {
+ throw expected.throw_error;
+ }
+ }
+
+}
+
+private interface ValaUnit.Argument {
+
+ public abstract void assert(GLib.Object object, string context)
+ throws GLib.Error;
+
+}
+
+private class ValaUnit.BoxArgument<T> : GLib.Object, Argument, TestAssertions {
+
+ private T value;
+
+ internal BoxArgument(T value) {
+ this.value = value;
+ }
+
+ public new void assert(GLib.Object object, string context)
+ throws GLib.Error {
+ assert_true(
+ object is BoxArgument,
+ "%s: Expected %s value".printf(context, this.get_type().name())
+ );
+ assert_true(this.value == ((BoxArgument<T>) object).value, context);
+ }
+
+}
+
+private class ValaUnit.IntArgument : GLib.Object, Argument, TestAssertions {
+
+ private int value;
+
+ internal IntArgument(int value) {
+ this.value = value;
+ }
+
+ public new void assert(GLib.Object object, string context)
+ throws GLib.Error {
+ assert_true(
+ object is IntArgument, "%s: Expected int value".printf(context)
+ );
+ assert_equal(((IntArgument) object).value, this.value, context);
+ }
+
+}
+
+private class ValaUnit.UintArgument : GLib.Object, Argument, TestAssertions {
+
+ private uint value;
+
+ internal UintArgument(uint value) {
+ this.value = value;
+ }
+
+ public new void assert(GLib.Object object, string context)
+ throws GLib.Error {
+ assert_true(
+ object is UintArgument, "%s: Expected uint value".printf(context)
+ );
+ assert_equal(((UintArgument) object).value, this.value, context);
+ }
+
+}
diff --git a/subprojects/vala-unit/src/test-adaptor.vala b/subprojects/vala-unit/src/test-adaptor.vala
new file mode 100644
index 000000000..3e45c7f48
--- /dev/null
+++ b/subprojects/vala-unit/src/test-adaptor.vala
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2009 Julien Peeters
+ * Copyright © 2017-2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ *
+ * Author(s):
+ * Julien Peeters <contact julienpeeters fr>
+ * Michael Gratton <mike vee net>
+ */
+
+
+/**
+ * A ValaUnit to GLib testing framework adaptor.
+ */
+internal class ValaUnit.TestAdaptor : GLib.Object {
+
+
+ public string name { get; private set; }
+ public TestCase test_case { get; private set; }
+
+ private TestCase.TestMethod test;
+
+
+ public TestAdaptor(string name,
+ owned TestCase.TestMethod test,
+ TestCase test_case) {
+ this.name = name;
+ this.test = (owned) test;
+ this.test_case = test_case;
+ }
+
+ public void set_up(void* fixture) {
+ try {
+ this.test_case.set_up();
+ } catch (GLib.Error err) {
+ log_error(err);
+ GLib.assert_not_reached();
+ }
+ }
+
+ public void run(void* fixture) {
+ try {
+ this.test();
+ } catch (TestError.SKIPPED err) {
+ GLib.Test.skip(err.message);
+ } catch (GLib.Error err) {
+ log_error(err);
+ GLib.Test.fail();
+ }
+ }
+
+ public void tear_down(void* fixture) {
+ try {
+ this.test_case.tear_down();
+ } catch (Error err) {
+ log_error(err);
+ GLib.assert_not_reached();
+ }
+ }
+
+ private void log_error(GLib.Error err) {
+ GLib.stderr.puts(this.test_case.name);
+ GLib.stderr.putc('/');
+
+ GLib.stderr.puts(this.name);
+ GLib.stderr.puts(": ");
+
+ GLib.stderr.puts(err.message);
+ GLib.stderr.putc('\n');
+ GLib.stderr.flush();
+ }
+
+}
diff --git a/subprojects/vala-unit/src/test-assertions.vala b/subprojects/vala-unit/src/test-assertions.vala
new file mode 100644
index 000000000..b14786019
--- /dev/null
+++ b/subprojects/vala-unit/src/test-assertions.vala
@@ -0,0 +1,533 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+namespace ValaUnit {
+
+ /** Error thrown when a test condition has failed */
+ public errordomain TestError {
+
+ /** Thrown when test assertion failed. */
+ FAILED,
+
+ /** Thrown when test has been skipped. */
+ SKIPPED,
+
+ /** Thrown when an assertion is not currently supported. */
+ UNSUPPORTED;
+
+ }
+
+ internal inline void assert_equal<T>(T actual,
+ T expected,
+ string? context = null)
+ throws TestError {
+ if ((actual == null && expected != null) ||
+ (actual != null && expected == null)) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ if (actual != null && expected != null) {
+ // Can't just do a direct comparison here, since under the
+ // hood we'll be comparing gconstpointers, which will
+ // nearly always be incorrect
+ var type = typeof(T);
+ if (type.is_object()) {
+ if (((GLib.Object) actual) !=
+ ((GLib.Object) expected)) {
+ ValaUnit.assert(
+ "%s are not equal".printf(typeof(T).name()),
+ context
+ );
+ }
+ } else if (type.is_enum()) {
+ assert_equal_enum<T>(actual, expected, context);
+ } else if (type == typeof(string)) {
+ assert_equal_string((string?) actual, (string?) expected, context);
+ } else if (type == typeof(int)) {
+ assert_equal_int((int?) actual, (int?) expected, context);
+ } else if (type == typeof(short)) {
+ assert_equal_short((short?) actual, (short?) expected, context);
+ } else if (type == typeof(char)) {
+ assert_equal_char((char?) actual, (char?) expected, context);
+ } else if (type == typeof(long)) {
+ assert_equal_long((long?) actual, (long?) expected, context);
+ } else if (type == typeof(int64)) {
+ assert_equal_int64((int64?) actual, (int64?) expected, context);
+ } else if (type == typeof(uint)) {
+ assert_equal_uint((uint?) actual, (uint?) expected, context);
+ } else if (type == typeof(uchar)) {
+ assert_equal_uchar((uchar?) actual, (uchar?) expected, context);
+ } else if (type == typeof(ushort)) {
+ assert_equal_ushort((ushort?) actual, (ushort?) expected, context);
+ } else if (type == typeof(ulong)) {
+ assert_equal_ulong((ulong?) actual, (ulong?) expected, context);
+ } else if (type == typeof(uint64)) {
+ assert_equal_uint64((uint64?) actual, (uint64?) expected, context);
+ } else if (type == typeof(double)) {
+ assert_equal_double((double?) actual, (double?) expected, context);
+ } else if (type == typeof(float)) {
+ assert_equal_float((float?) actual, (float?) expected, context);
+ } else if (type == typeof(bool)) {
+ assert_equal_bool((bool?) actual, (bool?) expected, context);
+ } else {
+ ValaUnit.assert(
+ "%s is not a supported type for equality tests".printf(
+ type.name()
+ ),
+ context
+ );
+ }
+ }
+ }
+
+ internal inline void assert(string message, string? context)
+ throws TestError {
+ var buf = new GLib.StringBuilder();
+ if (context != null) {
+ buf.append_c('[');
+ buf.append((string) context);
+ buf.append("] ");
+ }
+ buf.append(message);
+
+ throw new TestError.FAILED(buf.str);
+ }
+
+ /**
+ * Unpacks generics-based value types and repacks as boxed.
+ *
+ * Per GNOME/vala#564, non-boxed, non-pointer values will be
+ * passed as a pointer, where the memory address of the pointer is
+ * the actual value (!). This method works around that by casting
+ * back to a value, then boxing so that the value is allocated and
+ * passed by reference instead.
+ *
+ * This will only work when the values are not already boxed.
+ */
+ internal T box_value<T>(T value) {
+ var type = typeof(T);
+ T boxed = value;
+
+ if (type == typeof(int) || type.is_enum()) {
+ int actual = (int) value;
+ boxed = (int?) actual;
+ } else if (type == typeof(short)) {
+ short actual = (short) value;
+ boxed = (short?) actual;
+ } else if (type == typeof(char)) {
+ } else if (type == typeof(long)) {
+ } else if (type == typeof(int64)) {
+ } else if (type == typeof(uint)) {
+ } else if (type == typeof(uchar)) {
+ } else if (type == typeof(ushort)) {
+ } else if (type == typeof(ulong)) {
+ } else if (type == typeof(uint64)) {
+ } else if (type == typeof(double)) {
+ } else if (type == typeof(float)) {
+ } else if (type == typeof(bool)) {
+ }
+
+ return boxed;
+ }
+
+ internal string to_display_string<T>(T value) {
+ var type = typeof(T);
+ var display = "";
+
+ if (value == null) {
+ display = "(null)";
+ } else if (type == typeof(string)) {
+ display = "“%s”".printf((string) ((string?) value));
+ } else if (type.is_enum()) {
+ display = GLib.EnumClass.to_string(
+ typeof(T), (int) ((int?) value)
+ );
+ } else if (type == typeof(int)) {
+ display = ((int) ((int?) value)).to_string();
+ } else if (type == typeof(short)) {
+ display = ((short) ((short?) value)).to_string();
+ } else if (type == typeof(char)) {
+ display = "‘%s’".printf(((char) ((char?) value)).to_string());
+ } else if (type == typeof(long)) {
+ display = ((long) ((long?) value)).to_string();
+ } else if (type == typeof(int64)) {
+ display = ((int64) ((int64?) value)).to_string();
+ } else if (type == typeof(uint)) {
+ display = ((uint) ((uint?) value)).to_string();
+ } else if (type == typeof(uchar)) {
+ display = "‘%s’".printf(((uchar) ((uchar?) value)).to_string());
+ } else if (type == typeof(ushort)) {
+ display = ((ushort) ((ushort?) value)).to_string();
+ } else if (type == typeof(ulong)) {
+ display = ((long) ((long?) value)).to_string();
+ } else if (type == typeof(uint64)) {
+ display = ((uint64) ((uint64?) value)).to_string();
+ } else if (type == typeof(double)) {
+ display = ((double) ((double?) value)).to_string();
+ } else if (type == typeof(float)) {
+ display = ((float) ((float?) value)).to_string();
+ } else if (type == typeof(bool)) {
+ display = ((bool) ((bool?) value)).to_string();
+ } else {
+ display = type.name();
+ }
+
+ return display;
+ }
+
+ private inline void assert_is_not_equal<T>(T actual,
+ T expected,
+ string? context)
+ throws TestError {
+ assert(
+ "%s != %s".printf(
+ to_display_string(actual),
+ to_display_string(expected)
+ ),
+ context
+ );
+ }
+
+ private void assert_equal_enum<T>(T actual,
+ T expected,
+ string? context)
+ throws TestError {
+ int actual_val = (int) ((int?) actual);
+ int expected_val = (int) ((int?) expected);
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_string(string? actual,
+ string? expected,
+ string? context)
+ throws TestError {
+ string actual_val = (string) actual;
+ string expected_val = (string) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_int(int? actual, int? expected, string? context)
+ throws TestError {
+ int actual_val = (int) actual;
+ int expected_val = (int) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_char(char? actual, char? expected, string? context)
+ throws TestError {
+ char actual_val = (char) actual;
+ char expected_val = (char) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_short(short? actual, short? expected, string? context)
+ throws TestError {
+ short actual_val = (short) actual;
+ short expected_val = (short) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_long(long? actual, long? expected, string? context)
+ throws TestError {
+ long actual_val = (long) actual;
+ long expected_val = (long) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_int64(int64? actual, int64? expected, string? context)
+ throws TestError {
+ int64 actual_val = (int64) actual;
+ int64 expected_val = (int64) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_uint(uint? actual, uint? expected, string? context)
+ throws TestError {
+ uint actual_val = (uint) actual;
+ uint expected_val = (uint) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_uchar(uchar? actual, uchar? expected, string? context)
+ throws TestError {
+ uchar actual_val = (uchar) actual;
+ uchar expected_val = (uchar) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_ushort(ushort? actual, ushort? expected, string? context)
+ throws TestError {
+ ushort actual_val = (ushort) actual;
+ ushort expected_val = (ushort) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_ulong(ulong? actual, ulong? expected, string? context)
+ throws TestError {
+ ulong actual_val = (ulong) actual;
+ ulong expected_val = (ulong) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_uint64(uint64? actual, uint64? expected, string? context)
+ throws TestError {
+ uint64 actual_val = (uint64) actual;
+ uint64 expected_val = (uint64) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_float(float? actual, float? expected, string? context)
+ throws TestError {
+ float actual_val = (float) actual;
+ float expected_val = (float) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_double(double? actual, double? expected, string? context)
+ throws TestError {
+ double actual_val = (double) actual;
+ double expected_val = (double) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+ private void assert_equal_bool(bool? actual, bool? expected, string? context)
+ throws TestError {
+ bool actual_val = (bool) actual;
+ bool expected_val = (bool) expected;
+ if (actual_val != expected_val) {
+ assert_is_not_equal(actual, expected, context);
+ }
+ }
+
+}
+
+/**
+ * Defines default test assertions.
+ *
+ * Note that {@link TestCase} implements this, so when making
+ * assertions in test methods, you can just call these directly.
+ */
+public interface ValaUnit.TestAssertions : GLib.Object {
+
+
+ /** Asserts a value is null */
+ public void assert_non_null<T>(T actual, string? context = null)
+ throws TestError {
+ if (actual == null) {
+ ValaUnit.assert(
+ "%s is null, expected non-null".printf(typeof(T).name()),
+ context
+ );
+ }
+ }
+
+ /** Asserts a value is null */
+ public void assert_null<T>(T actual, string? context = null)
+ throws TestError {
+ if (actual != null) {
+ ValaUnit.assert(
+ "%s is non-null, expected null".printf(typeof(T).name()),
+ context
+ );
+ }
+ }
+
+ /** Asserts the two given values refer to the same object or value. */
+ public void assert_equal<T>(T actual, T expected, string? context = null)
+ throws TestError {
+ ValaUnit.assert_equal(actual, expected, context);
+ }
+
+ /** Asserts the two given values refer to the same object or value. */
+ public void assert_within(double actual,
+ double expected,
+ double epsilon,
+ string? context = null)
+ throws TestError {
+ if (actual > expected + epsilon || actual < expected - epsilon) {
+ ValaUnit.assert(
+ "%f is not within ±%f of %f".printf(actual, epsilon, expected),
+ context
+ );
+ }
+ }
+
+ /** Asserts a Boolean value is true. */
+ public void assert_true(bool actual, string? context = null)
+ throws TestError {
+ if (!actual) {
+ ValaUnit.assert("Is false, expected true", context);
+ }
+ }
+
+ /** Asserts a Boolean value is false. */
+ public void assert_false(bool actual, string? context = null)
+ throws TestError {
+ if (actual) {
+ ValaUnit.assert("Is true, expected false", context);
+ }
+ }
+
+ /** Asserts a collection is non-null and empty. */
+ public CollectionAssertions<string> assert_string(string? actual,
+ string? context = null)
+ throws TestError {
+ if (actual == null) {
+ ValaUnit.assert("Expected a string, was null", context);
+ }
+ return new StringCollectionAssertion((string) actual, context);
+ }
+
+ /** Asserts a collection is non-null and empty. */
+ public CollectionAssertions<E> assert_array<E>(E[]? actual,
+ string? context = null)
+ throws TestError {
+ if (actual == null) {
+ ValaUnit.assert("Expected an array, was null", context);
+ }
+ return new ArrayCollectionAssertion<E>((E[]) actual, context);
+ }
+
+ /** Asserts a collection is non-null and empty. */
+ public CollectionAssertions<E> assert_collection<E>(
+ Gee.Collection<E>? actual,
+ string? context = null
+ ) throws TestError {
+ if (actual == null) {
+ ValaUnit.assert("Expected a collection, was null", context);
+ }
+ return new GeeCollectionAssertion<E>(
+ (Gee.Collection<E>) actual, context
+ );
+ }
+
+ /** Asserts a comparator value is equal, that is, 0. */
+ public void assert_compare_eq(int actual, string? context = null)
+ throws TestError {
+ if (actual != 0) {
+ ValaUnit.assert(
+ "Comparison is not equal: %d".printf(actual), context
+ );
+ }
+ }
+
+ /** Asserts a comparator value is greater-than, that is, > 0. */
+ public void assert_compare_gt(int actual, string? context = null)
+ throws TestError {
+ if (actual < 0) {
+ ValaUnit.assert(
+ "Comparison is not greater than: %d".printf(actual), context
+ );
+ }
+ }
+
+ /** Asserts a comparator value is less-than, that is, < 0. */
+ public void assert_compare_lt(int actual, string? context = null)
+ throws TestError {
+ if (actual > 0) {
+ ValaUnit.assert(
+ "Comparison is not less than: %d".printf(actual), context
+ );
+ }
+ }
+
+ /**
+ * Asserts an error matches an expected type.
+ *
+ * The actual error's domain and code must be the same as that of
+ * the expected, but its message is ignored.
+ */
+ public void assert_error(GLib.Error? actual,
+ GLib.Error expected,
+ string? context = null) throws TestError {
+ if (actual == null) {
+ ValaUnit.assert(
+ "Expected error: %s %i, was null".printf(
+ expected.domain.to_string(), expected.code
+ ),
+ context
+ );
+ } else {
+ var non_null = (GLib.Error) actual;
+ if (expected.domain != non_null.domain ||
+ expected.code != non_null.code) {
+ ValaUnit.assert(
+ "Expected error: %s %i, was actually %s %i: %s".printf(
+ expected.domain.to_string(),
+ expected.code,
+ non_null.domain.to_string(),
+ non_null.code,
+ non_null.message
+ ),
+ context
+ );
+ }
+ }
+ }
+
+ public void assert_no_error(GLib.Error? err, string? context = null)
+ throws TestError {
+ if (err != null) {
+ var non_null = (GLib.Error) err;
+ ValaUnit.assert(
+ "Unexpected error: %s %i: %s".printf(
+ non_null.domain.to_string(),
+ non_null.code,
+ non_null.message
+ ),
+ context
+ );
+ }
+ }
+
+ // The following deliberately shadow un-prefixed GLib calls so as
+ // to get consistent behaviour when called
+
+ /**
+ * Asserts a Boolean value is true.
+ */
+ public void assert(bool actual, string? context = null)
+ throws TestError {
+ assert_true(actual, context);
+ }
+
+ /**
+ * Asserts this call is never made.
+ */
+ public void assert_not_reached(string? context = null)
+ throws TestError {
+ ValaUnit.assert("This call should not be reached", context);
+ }
+
+}
diff --git a/subprojects/vala-unit/src/test-case.vala b/subprojects/vala-unit/src/test-case.vala
new file mode 100644
index 000000000..9a7fc8882
--- /dev/null
+++ b/subprojects/vala-unit/src/test-case.vala
@@ -0,0 +1,207 @@
+/*
+ * Copyright © 2009 Julien Peeters
+ * Copyright © 2017-2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ *
+ * Author(s):
+ * Julien Peeters <contact julienpeeters fr>
+ * Michael Gratton <mike vee net>
+ */
+
+
+/**
+ * The primary class for creating unit tests.
+ *
+ * A test case is a collection of related test methods.
+ *
+ * To create and run tests, extend this class with one or more test
+ * methods that implement {@link TestMethod} and call {@link add_test}
+ * for each. These may then be added to the root {@link
+ * GLib.TestSuite} or a child test suite of the root, then executed by
+ * calling {@link GLib.Test.run}.
+ *
+ * To make test assertions in test methods, call the `assert` methods
+ * on this class instead of those defined by GLib.
+ */
+public abstract class ValaUnit.TestCase : GLib.Object, TestAssertions {
+
+
+ /** The delegate that test methods must implement. */
+ public delegate void TestMethod() throws GLib.Error;
+
+
+ private class SignalWaiter : Object {
+
+ public bool was_fired = false;
+
+ public void @callback(Object source) {
+ was_fired = true;
+ }
+ }
+
+
+ /** The name of this test case. */
+ public string name { get; private set; }
+
+ /** The collection of GLib tests defined by this test case. */
+ public GLib.TestSuite suite { get; private set; }
+
+ /** Main loop context for this test case. */
+ protected GLib.MainContext main_loop {
+ get; private set; default = GLib.MainContext.default();
+ }
+
+ private TestAdaptor[] adaptors = new TestAdaptor[0];
+ private AsyncResultWaiter async_waiter;
+
+
+ /**
+ * Constructs a new named test case.
+ *
+ * The given name is used as the name of the GLib test suite that
+ * collects all tests.
+ */
+ protected TestCase(string name) {
+ this.name = name;
+ this.suite = new GLib.TestSuite(name);
+ this.async_waiter = new AsyncResultWaiter(this.main_loop);
+ }
+
+ /**
+ * Test case fixture set-up method.
+ *
+ * This method is called prior to running a test method.
+ *
+ * Test cases should override this method when they require test
+ * fixtures to be initialised before a test is run.
+ */
+ public virtual void set_up() throws GLib.Error {
+ // no-op
+ }
+
+ /**
+ * Test case fixture set-up method.
+ *
+ * This method is called after a test method is successfully run.
+ *
+ * Test cases should override this method when they require test
+ * fixtures to be destroyed after a test is run.
+ */
+ public virtual void tear_down() throws GLib.Error {
+ // no-op
+ }
+
+ /**
+ * Adds a test method to be executed as part of this test case.
+ *
+ * Adding a test method add it to {@link suite} with the given
+ * name, ensuring the {@link set_up}, test, and {@link tear_down}
+ * methods are executed when the test suite is run.
+ */
+ protected void add_test(string name, owned TestMethod test) {
+ var adaptor = new TestAdaptor(name, (owned) test, this);
+ this.adaptors += adaptor;
+
+ this.suite.add(
+ new GLib.TestCase(
+ adaptor.name,
+ adaptor.set_up,
+ adaptor.run,
+ adaptor.tear_down
+ )
+ );
+ }
+
+ /**
+ * Calls the same method on the test case's default async waiter.
+ *
+ * @see AsyncResultWaiter.async_result
+ */
+ protected AsyncResult async_result() {
+ return this.async_waiter.async_result();
+ }
+
+ /**
+ * Calls the same method on the test case's default async waiter.
+ *
+ * @see AsyncResultWaiter.async_completion
+ */
+ protected void async_completion(GLib.Object? object,
+ AsyncResult result) {
+ this.async_waiter.async_completion(object, result);
+ }
+
+ /**
+ * Waits for a mock object's call to be completed.
+ *
+ * This method busy waits on the test's main loop until either
+ * until {@link ExpectedCall.was_called} is true, or until the
+ * given timeout in seconds has occurred.
+ *
+ * Returns //true// if the call was made, or //false// if the
+ * timeout was reached.
+ */
+ protected bool wait_for_call(ExpectedCall call, double timeout = 1.0) {
+ GLib.Timer timer = new GLib.Timer();
+ timer.start();
+ while (!call.was_called && timer.elapsed() < timeout) {
+ this.main_loop.iteration(false);
+ }
+ return call.was_called;
+ }
+
+ /**
+ * Waits for an object's signal to be fired.
+ *
+ * This method busy waits on the test's main loop until either
+ * until the object emits the named signal, or until the given
+ * timeout in seconds has occurred.
+ *
+ * Returns //true// if the signal was fired, or //false// if the
+ * timeout was reached.
+ */
+ protected bool wait_for_signal(GLib.Object source,
+ string name,
+ double timeout = 0.5) {
+ SignalWaiter handler = new SignalWaiter();
+ ulong id = GLib.Signal.connect_swapped(
+ source, name, (GLib.Callback) handler.callback, handler
+ );
+
+ GLib.Timer timer = new GLib.Timer();
+ timer.start();
+ while (!handler.was_fired && timer.elapsed() < timeout) {
+ this.main_loop.iteration(false);
+ }
+
+ source.disconnect(id);
+ return handler.was_fired;
+ }
+
+ /**
+ * Immediately causes the current test to fail.
+ *
+ * Throws a {@link TestError.FAILED} with the given reason,
+ * terminating the test.
+ */
+ protected void fail(string? message = null) throws TestError.FAILED {
+ throw new TestError.FAILED(
+ message != null ? (string) message : "Test failed"
+ );
+ }
+
+ /**
+ * Immediately skips the rest of the current test.
+ *
+ * Throws a {@link TestError.SKIPPED} with the given reason,
+ * terminating the test.
+ */
+ protected void skip(string? message = null) throws TestError.SKIPPED {
+ throw new TestError.SKIPPED(
+ message != null ? (string) message : "Test skipped"
+ );
+ }
+
+}
diff --git a/subprojects/vala-unit/test/collection-assertions.vala
b/subprojects/vala-unit/test/collection-assertions.vala
new file mode 100644
index 000000000..05102f961
--- /dev/null
+++ b/subprojects/vala-unit/test/collection-assertions.vala
@@ -0,0 +1,216 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class CollectionAssertions : ValaUnit.TestCase {
+
+
+
+ public CollectionAssertions() {
+ base("CollectionAssertions");
+ add_test("string_collection", string_collection);
+ add_test("string_array_collection", string_array_collection);
+ add_test("int_array_collection", int_array_collection);
+ add_test("string_gee_collection", string_gee_collection);
+ add_test("int_gee_collection", int_gee_collection);
+ }
+
+ public void string_collection() throws GLib.Error {
+ assert_string("hello", "non-empty string")
+ .is_non_empty()
+ .size(5)
+ .contains("lo")
+ .not_contains("☃")
+ .first_is("h")
+ .first_is("hell")
+ .at_index_is(1, "e")
+ .at_index_is(1, "ell");
+
+
+ assert_string("", "empty string")
+ .is_empty()
+ .size(0)
+ .contains("")
+ .not_contains("☃");
+
+ try {
+ assert_string("").is_non_empty();
+ fail("Expected ::is_non_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_string("hello").is_empty();
+ fail("Expected ::is_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_string("hello").contains("☃");
+ fail("Expected ::contains to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ public void string_array_collection() throws GLib.Error {
+ assert_array(new string[] { "hello", "world"})
+ .is_non_empty()
+ .size(2)
+ .contains("hello")
+ .not_contains("☃")
+ .first_is("hello")
+ .at_index_is(1, "world");
+
+
+ assert_array(new string[0])
+ .is_empty()
+ .size(0)
+ .not_contains("☃");
+
+ try {
+ assert_array(new string[0]).is_non_empty();
+ fail("Expected ::is_non_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_array(new string[] { "hello", "world"}).is_empty();
+ fail("Expected ::is_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_array(new string[] { "hello", "world"}).contains("☃");
+ fail("Expected ::contains to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ public void int_array_collection() throws GLib.Error {
+ skip("Arrays containing non-pointer values not currently supported. See GNOME/vala#964");
+ int[] array = new int[] { 42, 1337 };
+ int[] empty = new int[0];
+
+ assert_array(array)
+ .is_non_empty()
+ .size(2)
+ .contains(42)
+ .not_contains(-1)
+ .first_is(42)
+ .at_index_is(1, 1337);
+
+ assert_array(empty)
+ .is_empty()
+ .size(0)
+ .not_contains(42);
+
+ try {
+ assert_array(empty).is_non_empty();
+ fail("Expected ::is_non_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_array(array).is_empty();
+ fail("Expected ::is_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_array(array).contains(-1);
+ fail("Expected ::contains to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ public void string_gee_collection() throws GLib.Error {
+ var strv = new string[] { "hello", "world" };
+ assert_collection(new_gee_collection(strv))
+ .is_non_empty()
+ .size(2)
+ .contains("hello")
+ .not_contains("☃")
+ .first_is("hello")
+ .at_index_is(1, "world");
+
+ assert_collection(new_gee_collection(new string[0]))
+ .is_empty()
+ .size(0)
+ .not_contains("☃");
+
+ try {
+ assert_collection(new_gee_collection(new string[0])).is_non_empty();
+ fail("Expected ::is_non_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_collection(new_gee_collection(strv)).is_empty();
+ fail("Expected ::is_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_collection(new_gee_collection(strv)).contains("☃");
+ fail("Expected ::contains to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ public void int_gee_collection() throws GLib.Error {
+ var intv = new int[] { 42, 1337 };
+ assert_collection(new_gee_collection(intv))
+ .is_non_empty()
+ .size(2)
+ .contains(42)
+ .not_contains(-1)
+ .first_is(42)
+ .at_index_is(1, 1337);
+
+ assert_collection(new_gee_collection(new int[0]))
+ .is_empty()
+ .size(0)
+ .not_contains(42);
+
+ try {
+ assert_collection(new_gee_collection(new int[0])).is_non_empty();
+ fail("Expected ::is_non_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_collection(new_gee_collection(intv)).is_empty();
+ fail("Expected ::is_empty to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+
+ try {
+ assert_collection(new_gee_collection(intv)).contains(-1);
+ fail("Expected ::contains to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ private Gee.Collection<T> new_gee_collection<T>(T[] values) {
+ return new Gee.ArrayList<T>.wrap(values);
+ }
+
+}
diff --git a/subprojects/vala-unit/test/test-assertions.vala b/subprojects/vala-unit/test/test-assertions.vala
new file mode 100644
index 000000000..1e4cebd09
--- /dev/null
+++ b/subprojects/vala-unit/test/test-assertions.vala
@@ -0,0 +1,299 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class TestAssertions : ValaUnit.TestCase {
+
+
+ private class TestObject : GLib.Object { }
+
+ private enum TestEnum { CHECK, ONE, TWO; }
+
+ [Flags]
+ private enum TestFlags { CHECK, ONE, TWO; }
+
+ private struct TestStruct {
+ public string member;
+ }
+
+
+ public TestAssertions() {
+ base("TestAssertions");
+ add_test("gobject_equality_assertions", gobject_equality_assertions);
+ add_test("string_equality_assertions", string_equality_assertions);
+ add_test("int_equality_assertions", int_equality_assertions);
+ add_test("short_equality_assertions", short_equality_assertions);
+ add_test("long_equality_assertions", long_equality_assertions);
+ add_test("uint_equality_assertions", uint_equality_assertions);
+ add_test("float_equality_assertions", float_equality_assertions);
+ add_test("double_equality_assertions", double_equality_assertions);
+ add_test("char_equality_assertions", char_equality_assertions);
+ add_test("unichar_equality_assertions", unichar_equality_assertions);
+ add_test("enum_equality_assertions", enum_equality_assertions);
+ add_test("bool_equality_assertions", bool_equality_assertions);
+ add_test("struct_equality_assertions", struct_equality_assertions);
+ add_test("string_collection", string_collection);
+ add_test("array_collection", array_collection);
+ add_test("gee_collection", gee_collection);
+ }
+
+ public void gobject_equality_assertions() throws GLib.Error {
+ TestObject o1 = new TestObject();
+ TestObject o2 = new TestObject();
+
+ expect_equal_success(o1, o1);
+ expect_equal_failure(o1, o2);
+ }
+
+ public void string_equality_assertions() throws GLib.Error {
+ // Consts
+ expect_equal_success("foo", "foo");
+ expect_equal_failure("foo", "bar");
+
+ // Variables
+ var foo1 = "foo";
+ var foo2 = "foo";
+ var bar = "bar";
+ expect_equal_success(foo1, foo1);
+ expect_equal_success(foo1, foo2);
+ expect_equal_failure(foo1, bar);
+
+ // Boxing variations
+ expect_equal_success<string?>(foo1, foo1);
+ expect_equal_success<string?>(foo1, foo2);
+ expect_equal_failure<string?>(foo1, bar);
+ expect_equal_success<string?>("foo", "foo");
+ expect_equal_failure<string>("foo", "bar");
+ expect_equal_success((string?) foo1, (string?) foo1);
+ expect_equal_success((string?) foo1, (string?) foo2);
+ expect_equal_failure((string?) foo1, (string?) bar);
+ expect_equal_success((string?) "foo", (string?) "foo");
+ expect_equal_failure((string?) "foo", (string?) "bar");
+ }
+
+ public void int_equality_assertions() throws GLib.Error {
+ // Consts
+ expect_equal_success<int?>(42, 42);
+ expect_equal_failure<int?>(1337, -1);
+
+ // Variables
+ int forty_two_a = 42;
+ int forty_two_b = 42;
+ int l33t = 1337;
+ int neg = -1;
+ expect_equal_success<int?>(forty_two_a, forty_two_a);
+ expect_equal_success<int?>(forty_two_a, forty_two_b);
+ expect_equal_failure<int?>(l33t, neg);
+ }
+
+ public void short_equality_assertions() throws GLib.Error {
+ skip("Cannot determine if a variable is a short. See GNOME/vala#993");
+
+ // Consts
+ expect_equal_success<short?>(42, 42);
+ expect_equal_failure<short?>(1337, -1);
+
+ // Variables
+ short forty_two_a = 42;
+ short forty_two_b = 42;
+ short l33t = 1337;
+ short neg = -1;
+ expect_equal_success<short?>(forty_two_a, forty_two_a);
+ expect_equal_success<short?>(forty_two_a, forty_two_b);
+ expect_equal_failure<short?>(l33t, neg);
+ }
+
+ public void long_equality_assertions() throws GLib.Error {
+ // Consts
+ expect_equal_success<long?>(42, 42);
+ expect_equal_failure<long?>(1337, -1);
+
+ // Variables
+ long forty_two_a = 42;
+ long forty_two_b = 42;
+ long l33t = 1337;
+ long neg = -1;
+ expect_equal_success<long?>(forty_two_a, forty_two_a);
+ expect_equal_success<long?>(forty_two_a, forty_two_b);
+ expect_equal_failure<long?>(l33t, neg);
+ }
+
+ public void int64_equality_assertions() throws GLib.Error {
+ // Consts
+ expect_equal_success<int64?>(42, 42);
+ expect_equal_failure<int64?>(1337, -1);
+
+ // Variables
+ int64 forty_two_a = 42;
+ int64 forty_two_b = 42;
+ int64 l33t = 1337;
+ int64 neg = -1;
+ expect_equal_success<int64?>(forty_two_a, forty_two_a);
+ expect_equal_success<int64?>(forty_two_a, forty_two_b);
+ expect_equal_failure<int64?>(l33t, neg);
+
+ // Boundary tests
+ var max = int64.MAX;
+ var min = int64.MIN;
+ expect_equal_success<int64?>(max, max);
+ expect_equal_success<int64?>(min, min);
+ expect_equal_failure<int64?>(min, max);
+ expect_equal_failure<int64?>(max, min);
+ }
+
+ public void uint_equality_assertions() throws GLib.Error {
+ // Consts
+ expect_equal_success<uint?>(42, 42);
+ expect_equal_failure<uint?>(1337, -1);
+
+ // Variables
+ int forty_two_a = 42;
+ int forty_two_b = 42;
+ int l33t = 1337;
+ int neg = -1;
+ expect_equal_success<uint?>(forty_two_a, forty_two_a);
+ expect_equal_success<uint?>(forty_two_a, forty_two_b);
+ expect_equal_failure<uint?>(l33t, neg);
+ }
+
+ public void float_equality_assertions() throws GLib.Error {
+ // Consts
+ //
+ expect_equal_success<float?>(42.0f, 42.0f);
+ expect_equal_failure<float?>(1337.0f, (-1.0f));
+
+ // Variables
+ float forty_two_a = 42.0f;
+ float forty_two_b = 42.0f;
+ float l33t = 1337.0f;
+ float neg = -1.0f;
+ expect_equal_success<float?>(forty_two_a, forty_two_a);
+ expect_equal_success<float?>(forty_two_a, forty_two_b);
+ expect_equal_failure<float?>(l33t, neg);
+
+ // Boundary tests
+ var max = float.MAX;
+ var min = float.MIN;
+ expect_equal_success<float?>(max, max);
+ expect_equal_success<float?>(min, min);
+ expect_equal_failure<float?>(min, max);
+ expect_equal_failure<float?>(max, min);
+ }
+
+ public void double_equality_assertions() throws GLib.Error {
+ // Consts
+ //
+ expect_equal_success<double?>(42.0, 42.0);
+ expect_equal_failure<double?>(1337.0, -1.0);
+
+ // Variables
+ double forty_two_a = 42.0;
+ double forty_two_b = 42.0;
+ double l33t = 1337.0;
+ double neg = -1.0;
+ expect_equal_success<double?>(forty_two_a, forty_two_a);
+ expect_equal_success<double?>(forty_two_a, forty_two_b);
+ expect_equal_failure<double?>(l33t, neg);
+
+ // Boundary tests
+ var max = double.MAX;
+ var min = double.MIN;
+ expect_equal_success<double?>(max, max);
+ expect_equal_success<double?>(min, min);
+ expect_equal_failure<double?>(min, max);
+ expect_equal_failure<double?>(max, min);
+ }
+
+ public void char_equality_assertions() throws GLib.Error {
+ expect_equal_success<char?>('a', 'a');
+ expect_equal_failure<char?>('a', 'b');
+ }
+
+ public void unichar_equality_assertions() throws GLib.Error {
+ expect_equal_success<unichar?>('☃', '☃');
+ expect_equal_failure<unichar?>('❄', '❅');
+ }
+
+ public void enum_equality_assertions() throws GLib.Error {
+ expect_equal_success<TestEnum?>(ONE, ONE);
+ expect_equal_failure<TestEnum?>(ONE, TWO);
+ }
+
+ public void bool_equality_assertions() throws GLib.Error {
+ expect_equal_success<bool?>(true, true);
+ expect_equal_success<bool?>(false, false);
+
+ expect_equal_failure<bool?>(true, false);
+ expect_equal_failure<bool?>(false, true);
+ }
+
+ public void struct_equality_assertions() throws GLib.Error {
+ var foo = TestStruct() { member = "foo" };
+
+ expect_equal_failure<TestStruct?>(foo, foo);
+
+ // Silence the build warning about `member` being unused
+ foo.member += "";
+ }
+
+ public void string_collection() throws GLib.Error {
+ assert_string("a");
+ try {
+ assert_string(null);
+ fail("Expected null string collection assertion to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ public void array_collection() throws GLib.Error {
+ assert_array(new string[] { "a" });
+ try {
+ assert_array<string>(null);
+ fail("Expected null array collection assertion to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ public void gee_collection() throws GLib.Error {
+ assert_collection(new_gee_collection(new string[] { "a" }));
+ try {
+ assert_collection<Gee.ArrayList<string>>(null);
+ fail("Expected null Gee collection assertion to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ private void expect_equal_success<T>(T actual,
+ T expected,
+ string? context = null)
+ throws GLib.Error {
+ try {
+ assert_equal(actual, expected, context);
+ } catch (ValaUnit.TestError.FAILED err) {
+ fail(@"Expected equal test to succeed: $(err.message)");
+ }
+ }
+
+ private void expect_equal_failure<T>(T actual,
+ T expected,
+ string? context = null)
+ throws GLib.Error {
+ try {
+ assert_equal(actual, expected, context);
+ fail("Expected equal test to fail");
+ } catch (ValaUnit.TestError.FAILED err) {
+ // all good
+ }
+ }
+
+ private Gee.Collection<T> new_gee_collection<T>(T[] values) {
+ return new Gee.ArrayList<T>.wrap(values);
+ }
+
+}
diff --git a/subprojects/vala-unit/test/test-driver.vala b/subprojects/vala-unit/test/test-driver.vala
new file mode 100644
index 000000000..9b6f36981
--- /dev/null
+++ b/subprojects/vala-unit/test/test-driver.vala
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+int main(string[] args) {
+ Test.init(ref args);
+
+ TestSuite root = TestSuite.get_root();
+ root.add_suite(new TestAssertions().suite);
+ root.add_suite(new CollectionAssertions().suite);
+
+ MainLoop loop = new MainLoop ();
+
+ int ret = -1;
+ Idle.add(() => {
+ ret = Test.run();
+ loop.quit();
+ return false;
+ });
+
+ loop.run();
+ return ret;
+}
diff --git a/test/meson.build b/test/meson.build
index 57f33a863..736e9e8ca 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,13 +1,8 @@
subdir('data')
-geary_test_lib_sources = [
- 'mock-object.vala',
- 'test-case.vala',
- 'test-server.vala',
-]
-
geary_test_engine_sources = [
'test-engine.vala',
+ 'test-server.vala',
# These should be included in the test lib sources, but we can't
# since that would make the test lib depend on geary-engine.vapi,
@@ -109,25 +104,11 @@ geary_test_integration_sources = [
'integration/smtp/client-session.vala',
]
-# Test library
-
-geary_test_lib_dependencies = [
- gee,
- gio
-]
-
-geary_test_lib = static_library('test-lib',
- geary_test_lib_sources,
- dependencies: geary_test_lib_dependencies,
- include_directories: config_h_dir,
- vala_args: geary_vala_args,
- c_args: geary_c_args,
-)
-
# Engine tests
geary_test_engine_dependencies = [
- geary_engine_internal_dep
+ geary_engine_internal_dep,
+ vala_unit_dep,
]
geary_test_engine_dependencies += geary_engine_dependencies
@@ -142,7 +123,6 @@ endif
geary_test_engine_bin = executable('test-engine',
geary_test_engine_sources,
- link_with: geary_test_lib,
dependencies: geary_test_engine_dependencies,
include_directories: config_h_dir,
vala_args: geary_test_engine_vala_args,
@@ -152,14 +132,14 @@ geary_test_engine_bin = executable('test-engine',
# Client tests
geary_test_client_dependencies = [
- geary_client_dep
+ geary_client_dep,
+ vala_unit_dep,
]
geary_test_client_dependencies += geary_client_dependencies
geary_test_client_bin = executable('test-client',
geary_test_client_sources,
dependencies: geary_test_client_dependencies,
- link_with: geary_test_lib,
include_directories: config_h_dir,
vala_args: geary_vala_args,
c_args: geary_c_args,
@@ -174,9 +154,9 @@ geary_test_integration_bin = executable('test-integration',
gee,
gio,
gmime,
+ vala_unit_dep,
webkit2gtk,
],
- link_with: geary_test_lib,
include_directories: config_h_dir,
vala_args: geary_vala_args,
c_args: geary_c_args,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]