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
This commit is contained in:
parent
e4edf28e0c
commit
6b1bad28b9
18 changed files with 3078 additions and 930 deletions
|
|
@ -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
|
||||
|
|
|
|||
464
subprojects/vala-unit/COPYING
Normal file
464
subprojects/vala-unit/COPYING
Normal file
|
|
@ -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
|
||||
|
||||
6
subprojects/vala-unit/README.md
Normal file
6
subprojects/vala-unit/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
ValaUnit
|
||||
========
|
||||
|
||||
A Glib-based, xUnit-style unit testing and mock object framework for
|
||||
Vala projects.
|
||||
152
subprojects/vala-unit/meson.build
Normal file
152
subprojects/vala-unit/meson.build
Normal file
|
|
@ -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)
|
||||
12
subprojects/vala-unit/meson_options.txt
Normal file
12
subprojects/vala-unit/meson_options.txt
Normal file
|
|
@ -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).'
|
||||
)
|
||||
108
subprojects/vala-unit/src/async-result-waiter.vala
Normal file
108
subprojects/vala-unit/src/async-result-waiter.vala
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
499
subprojects/vala-unit/src/collection-assertions.vala
Normal file
499
subprojects/vala-unit/src/collection-assertions.vala
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
148
subprojects/vala-unit/src/expected-call.vala
Normal file
148
subprojects/vala-unit/src/expected-call.vala
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
318
subprojects/vala-unit/src/mock-object.vala
Normal file
318
subprojects/vala-unit/src/mock-object.vala
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
75
subprojects/vala-unit/src/test-adaptor.vala
Normal file
75
subprojects/vala-unit/src/test-adaptor.vala
Normal file
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
533
subprojects/vala-unit/src/test-assertions.vala
Normal file
533
subprojects/vala-unit/src/test-assertions.vala
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
207
subprojects/vala-unit/src/test-case.vala
Normal file
207
subprojects/vala-unit/src/test-case.vala
Normal file
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
216
subprojects/vala-unit/test/collection-assertions.vala
Normal file
216
subprojects/vala-unit/test/collection-assertions.vala
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
299
subprojects/vala-unit/test/test-assertions.vala
Normal file
299
subprojects/vala-unit/test/test-assertions.vala
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
26
subprojects/vala-unit/test/test-driver.vala
Normal file
26
subprojects/vala-unit/test/test-driver.vala
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,442 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
private interface Argument {
|
||||
|
||||
public abstract void assert(Object object) throws Error;
|
||||
|
||||
}
|
||||
|
||||
private class BoxArgument<T> : Object, Argument {
|
||||
|
||||
private T value;
|
||||
|
||||
internal BoxArgument(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public new void assert(Object object) throws Error {
|
||||
assert_true(
|
||||
object is BoxArgument,
|
||||
"Expected %s value".printf(this.get_type().name())
|
||||
);
|
||||
assert_true(this.value == ((BoxArgument<T>) object).value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class IntArgument : Object, Argument {
|
||||
|
||||
private int value;
|
||||
|
||||
internal IntArgument(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public new void assert(Object object) throws Error {
|
||||
assert_true(object is IntArgument, "Expected int value");
|
||||
assert_int(this.value, ((IntArgument) object).value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class UintArgument : Object, Argument {
|
||||
|
||||
private uint value;
|
||||
|
||||
internal UintArgument(uint value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public new void assert(Object object) throws Error {
|
||||
assert_true(object is UintArgument, "Expected uint value");
|
||||
assert_uint(this.value, ((UintArgument) object).value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an expected method call on a mock object.
|
||||
*
|
||||
* An instance of this object is returned when calling {@link
|
||||
* Mock.Object.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 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, 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(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 GLib.Error {
|
||||
assert_true(
|
||||
this.async_callback != null,
|
||||
"Async call not called, could not resume"
|
||||
);
|
||||
assert_false(
|
||||
this.async_resumed,
|
||||
"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 GLib.Error {
|
||||
assert_true(
|
||||
this.called_args != null && this.called_args.length >= (pos + 1),
|
||||
"%s call argument %u, type %s, not present".printf(
|
||||
this.name, pos, typeof(T).name()
|
||||
)
|
||||
);
|
||||
assert_true(
|
||||
this.called_args[pos] is T,
|
||||
"%s call argument %u not of type %s".printf(
|
||||
this.name, pos, typeof(T).name()
|
||||
)
|
||||
);
|
||||
return (T) this.called_args[pos];
|
||||
}
|
||||
|
||||
internal void called(Object[]? args) {
|
||||
this.was_called = true;
|
||||
this.called_args = args;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Denotes a class that is 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 MockObject : GLib.Object {
|
||||
|
||||
|
||||
public static Object box_arg<T>(T value) {
|
||||
return new BoxArgument<T>(value);
|
||||
}
|
||||
|
||||
public static Object int_arg(int value) {
|
||||
return new IntArgument(value);
|
||||
}
|
||||
|
||||
public static Object uint_arg(uint value) {
|
||||
return new UintArgument(value);
|
||||
}
|
||||
|
||||
protected abstract Gee.Queue<ExpectedCall> expected { get; set; }
|
||||
|
||||
|
||||
public ExpectedCall expect_call(string name, Object[]? args = null) {
|
||||
ExpectedCall expected = new ExpectedCall(name, args);
|
||||
this.expected.offer(expected);
|
||||
return expected;
|
||||
}
|
||||
|
||||
public void assert_expectations() throws 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, 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,
|
||||
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, 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,
|
||||
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,
|
||||
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,
|
||||
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, Object[] args) throws GLib.Error {
|
||||
ExpectedCall? expected = call_made(name, args);
|
||||
check_for_exception(expected);
|
||||
}
|
||||
|
||||
protected async void void_call_async(string name, 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, Object[] args) throws Error {
|
||||
assert_false(this.expected.is_empty, "Unexpected call: %s".printf(name));
|
||||
|
||||
ExpectedCall expected = this.expected.poll();
|
||||
assert_string(expected.name, name, "Unexpected call");
|
||||
if (expected.expected_args != null) {
|
||||
assert_args(expected.expected_args, args, "Call %s".printf(name));
|
||||
}
|
||||
|
||||
expected.called(args);
|
||||
return expected;
|
||||
}
|
||||
|
||||
private void assert_args(Object[]? expected_args, Object[]? actual_args, string context)
|
||||
throws Error {
|
||||
int args = 0;
|
||||
foreach (Object expected in expected_args) {
|
||||
if (args >= actual_args.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
Object actual = actual_args[args];
|
||||
string arg_context = "%s, argument #%d".printf(context, args++);
|
||||
|
||||
if (expected is Argument) {
|
||||
((Argument) expected).assert(actual);
|
||||
} else if (expected != null) {
|
||||
assert_true(
|
||||
actual != null,
|
||||
"%s: Expected %s, actual is null".printf(
|
||||
arg_context, expected.get_type().name()
|
||||
)
|
||||
);
|
||||
assert_true(
|
||||
expected.get_type() == actual.get_type(),
|
||||
"%s: Expected %s, actual: %s".printf(
|
||||
arg_context,
|
||||
expected.get_type().name(),
|
||||
actual.get_type().name()
|
||||
)
|
||||
);
|
||||
assert_equal(
|
||||
expected, actual,
|
||||
"%s: object value".printf(arg_context)
|
||||
);
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
assert_int(
|
||||
expected_args.length, actual_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 = 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,462 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009 Julien Peeters
|
||||
* Copyright (C) 2017-2018 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Author:
|
||||
* Julien Peeters <contact@julienpeeters.fr>
|
||||
* Michael Gratton <mike@vee.net>
|
||||
*/
|
||||
|
||||
|
||||
public void assert_null(Object? actual, string? context = null)
|
||||
throws Error {
|
||||
if (actual != null) {
|
||||
print_assert(context ?? "Object is non-null", null);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_non_null(Object? actual, string? context = null)
|
||||
throws Error {
|
||||
if (actual == null) {
|
||||
print_assert(context ?? "Object is null", null);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_equal(Object expected, Object? actual, string? context = null)
|
||||
throws Error {
|
||||
if (expected != actual) {
|
||||
print_assert(context ?? "Objects are not equal", null);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_string(string expected, string? actual, string? context = null)
|
||||
throws Error {
|
||||
if (expected != actual) {
|
||||
string a = expected;
|
||||
if (a.length > 32) {
|
||||
a = a[0:32] + "…";
|
||||
}
|
||||
string? b = actual;
|
||||
if (b != null) {
|
||||
if (b.length > 32) {
|
||||
b = b[0:32] + "…";
|
||||
}
|
||||
}
|
||||
if (b != null) {
|
||||
print_assert("Expected: \"%s\", was: \"%s\"".printf(a, b), context);
|
||||
} else {
|
||||
print_assert("Expected: \"%s\", was null".printf(a), context);
|
||||
}
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_null_string(string? actual, string? context = null)
|
||||
throws Error {
|
||||
if (actual != null) {
|
||||
string a = actual;
|
||||
if (a.length > 70) {
|
||||
a = a[0:70] + "…";
|
||||
}
|
||||
print_assert("Expected: null, was: \"%s\"".printf(a), context);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_int(int expected, int actual, string? context = null)
|
||||
throws Error {
|
||||
if (expected != actual) {
|
||||
print_assert("Expected: %d, was: %d".printf(expected, actual), context);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_int64(int64 expected, int64 actual, string? context = null)
|
||||
throws Error {
|
||||
if (expected != actual) {
|
||||
print_assert("Expected: %lld, was: %lld".printf(expected, actual), context);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_double(double actual, double expected, double epsilon) {
|
||||
assert(actual + epsilon >= expected && actual - epsilon <= expected);
|
||||
}
|
||||
|
||||
public void assert_uint(uint expected, uint actual, string? context = null)
|
||||
throws GLib.Error {
|
||||
if (expected != actual) {
|
||||
print_assert("Expected: %u, was: %u".printf(expected, actual), context);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_true(bool condition, string? context = null)
|
||||
throws Error {
|
||||
if (!condition) {
|
||||
print_assert(context ?? "Expected true", null);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_false(bool condition, string? context = null)
|
||||
throws Error {
|
||||
if (condition) {
|
||||
print_assert(context ?? "Expected false", null);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void assert_error(Error expected, Error? actual, string? context = null) {
|
||||
bool failed = false;
|
||||
if (actual == null) {
|
||||
print_assert(
|
||||
"Expected error: %s %i, was null".printf(
|
||||
expected.domain.to_string(), expected.code
|
||||
),
|
||||
context
|
||||
);
|
||||
failed = true;
|
||||
} else if (expected.domain != actual.domain ||
|
||||
expected.code != actual.code) {
|
||||
print_assert(
|
||||
"Expected error: %s %i, was actually %s %i: %s".printf(
|
||||
expected.domain.to_string(),
|
||||
expected.code,
|
||||
actual.domain.to_string(),
|
||||
actual.code,
|
||||
actual.message
|
||||
),
|
||||
context
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
// XXX this shadows GLib.assert_no_error since that doesn't work
|
||||
public void assert_no_error(Error? err, string? context = null) {
|
||||
if (err != null) {
|
||||
print_assert(
|
||||
"Unexpected error: %s %i: %s".printf(
|
||||
err.domain.to_string(),
|
||||
err.code,
|
||||
err.message
|
||||
),
|
||||
context
|
||||
);
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
private inline void print_assert(string message, string? context) {
|
||||
string output = message;
|
||||
if (context != null) {
|
||||
output = "%s: %s".printf(context, output);
|
||||
}
|
||||
GLib.stderr.puts(output);
|
||||
GLib.stderr.putc('\n');
|
||||
}
|
||||
|
||||
public void delete_file(File parent) throws GLib.Error {
|
||||
FileInfo info = parent.query_info(
|
||||
"standard::*",
|
||||
FileQueryInfoFlags.NOFOLLOW_SYMLINKS
|
||||
);
|
||||
|
||||
if (info.get_file_type () == FileType.DIRECTORY) {
|
||||
FileEnumerator enumerator = parent.enumerate_children(
|
||||
"standard::*",
|
||||
FileQueryInfoFlags.NOFOLLOW_SYMLINKS
|
||||
);
|
||||
|
||||
info = null;
|
||||
while (((info = enumerator.next_file()) != null)) {
|
||||
delete_file(parent.get_child(info.get_name()));
|
||||
}
|
||||
}
|
||||
|
||||
parent.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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_complete} 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 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 result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract class TestCase : GLib.Object {
|
||||
|
||||
|
||||
/** GLib.File URI for resources in test/data. */
|
||||
public const string RESOURCE_URI = "resource:///org/gnome/GearyTest";
|
||||
|
||||
|
||||
private class SignalWaiter : Object {
|
||||
|
||||
public bool was_fired = false;
|
||||
|
||||
public void @callback(Object source) {
|
||||
was_fired = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected MainContext main_loop = MainContext.default();
|
||||
|
||||
private GLib.TestSuite suite;
|
||||
private Adaptor[] adaptors = new Adaptor[0];
|
||||
private AsyncResultWaiter async_waiter;
|
||||
|
||||
public delegate void TestMethod() throws Error;
|
||||
|
||||
protected TestCase(string name) {
|
||||
this.suite = new GLib.TestSuite(name);
|
||||
this.async_waiter = new AsyncResultWaiter(this.main_loop);
|
||||
}
|
||||
|
||||
public void add_test(string name, owned TestMethod test) {
|
||||
var adaptor = new Adaptor(name, (owned) test, this);
|
||||
this.adaptors += adaptor;
|
||||
|
||||
this.suite.add(
|
||||
new GLib.TestCase(
|
||||
adaptor.name,
|
||||
adaptor.set_up,
|
||||
adaptor.run,
|
||||
adaptor.tear_down
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public virtual void set_up() throws Error {
|
||||
}
|
||||
|
||||
public virtual void tear_down() throws Error {
|
||||
}
|
||||
|
||||
public GLib.TestSuite get_suite() {
|
||||
return this.suite;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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;
|
||||
}
|
||||
|
||||
private class Adaptor {
|
||||
|
||||
public string name { get; private set; }
|
||||
private TestMethod test;
|
||||
private TestCase test_case;
|
||||
|
||||
public Adaptor(string name,
|
||||
owned 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 (Error err) {
|
||||
assert_no_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
public void run(void* fixture) {
|
||||
try {
|
||||
this.test();
|
||||
} catch (Error err) {
|
||||
assert_no_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
public void tear_down(void* fixture) {
|
||||
try {
|
||||
this.test_case.tear_down();
|
||||
} catch (Error err) {
|
||||
assert_no_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue